simple-gnupg-keyserver 1.1.0 → 1.2.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 (3) hide show
  1. data/README.rdoc +4 -3
  2. data/lib/simpleHKP.rb +102 -79
  3. metadata +1 -1
@@ -18,14 +18,14 @@ Ubuntu}[http://www.bauer-power.net/2010/05/how-to-setup-free-pgp-key-server-in.h
18
18
 
19
19
  === Key storage
20
20
 
21
- Since this is a _simple_ key server, all keys are stored as flat
22
- ASCII armored files in the "keys" directory.
21
+ Since this is a _simple_ key server, all keys are imported into a GnuPG
22
+ keyring located in the "keys" directory.
23
23
 
24
24
  === Key search
25
25
 
26
26
  For each uploaded key, the key's "gpg2 --with-fingerprint --with-colon"
27
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
28
+ keyID name as hash key. All searches become Ruby regular expressions
29
29
  which are matched against each key's key data.
30
30
 
31
31
  === Security:
@@ -69,6 +69,7 @@ The current default options are:
69
69
 
70
70
  defaultOptions = {
71
71
  'debug' => false, # should debug output to logged?
72
+ 'title' => 'SimpleHKP' # the title used by the default headerHTML
72
73
  'simpleHKPdir' => 'simpleHKP', # base disk path to simpleHKP disk space
73
74
  'keyDir' => 'keys', # subdir to key storage directory
74
75
  'mediaDir' => 'media', # subdir to any css, js, images etc
@@ -1,4 +1,5 @@
1
1
  require 'uri'
2
+ require 'open3'
2
3
  require 'pp'
3
4
  require 'fileutils'
4
5
 
@@ -40,16 +41,47 @@ require 'fileutils'
40
41
 
41
42
  class SimpleHKP
42
43
 
43
- VERSION = "1.1.0"
44
+ VERSION = "1.2.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
44
53
 
45
54
  def loadKeys
46
55
  @keyLookupData = Hash.new
47
56
  puts "SimpleHKP: loading keys" if @debug
48
- Dir.glob(@keyDir+'/**/*.asc') do | aFile |
49
- puts aFile if @debug
50
- @keyLookupData[aFile] = `gpg2 --with-fingerprint --with-colons #{aFile}`
51
- puts @keyLookupData[aFile] 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
52
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
53
85
  puts "SimpleHKP: finished loading keys" if @debug
54
86
  end
55
87
 
@@ -152,6 +184,7 @@ class SimpleHKP
152
184
  # ensure the required directories all exist
153
185
  #
154
186
  FileUtils.mkdir_p(@keyDir)
187
+ FileUtils.chmod(0700, @keyDir)
155
188
  FileUtils.mkdir_p(@mediaDir)
156
189
  FileUtils.mkdir_p(@htmlDir)
157
190
  #
@@ -243,7 +276,27 @@ class SimpleHKP
243
276
  keys[0][0] =~ /keytext/
244
277
  pp keys[0][1] if @debug
245
278
  #
246
- # send the key data through gpg2 to extract the keyID
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
247
300
  #
248
301
  gpgKeyData = ""
249
302
  IO.popen('gpg2 --with-fingerprint --with-colons -', 'r+') do | pipe |
@@ -256,34 +309,24 @@ class SimpleHKP
256
309
  #
257
310
  # look for the keyID of the public key
258
311
  #
259
- keyID = nil
312
+ keyID = nil
260
313
  gpgKeyData.each_line do | aLine |
261
314
  keyData = aLine.split(/:/)
262
315
  next unless keyData[0] =~ /pub/
263
- keyID = keyData[4]
316
+ keyID = keyData[4]
264
317
  break
265
318
  end
266
319
  return replyBadRequest("No keyID found in gpg2 result using uploaded keytext data") if keyID.nil?
267
320
  #
268
- # record the fact that we have stored this key for later lookup
269
- #
270
- keyFile = "#{@keyDir}/#{keyID}.asc"
271
- @keyLookupData[keyFile] = gpgKeyData
272
- pp @keyLookupData if @debug
273
- #
274
- # store this key as a flat file with the name of the keyID.asc
275
- #
276
- File.open(keyFile,'w') do | keyFile |
277
- keyFile.write(keys[0][1])
278
- end
279
- #
280
321
  # return OK
281
322
  #
282
323
  @statusCode = 200
283
324
  @body << @headerHtml
284
325
  @body << "<h1 class=\"simpleHKP-storedKey\">Stored key: [#{keyID}]</h1>"
326
+ @body << "<p>key #{keyChanged}</p>"
285
327
  @body << "<h2 class=\"simpleHKP-keyDataKeyID\">Key data for key: [#{keyID}]</h2>"
286
- @body << '<pre class="simpleHKP-keyData">'+gpgKeyData+"</pre>\n"
328
+ @body << '<pre class="simpleHKP-keyData">'+@keyLookupData[keyID]['colonData']+"</pre>\n"
329
+ @body << '<pre class="simpleHKP-keyData">'+@keyLookupData[keyID]['humanData']+"</pre>\n"
287
330
  @body << @footer
288
331
  end
289
332
 
@@ -302,14 +345,14 @@ class SimpleHKP
302
345
  # (linearly) look through the hash of known keys
303
346
  # looking for the FIRST match
304
347
  #
305
- keyFile = nil
306
- @keyLookupData.each_pair do | aKeyFile, keyData |
307
- next unless keyData =~ searchRegexp
308
- puts "FOUND #{aKeyFile} (#{keyData})" if @debug
309
- keyFile = aKeyFile
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
310
353
  break
311
354
  end
312
- return replyNotFound if keyFile.nil?
355
+ return replyNotFound if keyID.nil?
313
356
 
314
357
  if queryString.has_key?('options') &&
315
358
  queryString['options'] == 'mr' then
@@ -319,52 +362,29 @@ class SimpleHKP
319
362
  @header = {
320
363
  'Content-Type' => 'application/pgp-keys; charset=utf-8',
321
364
  'Content-Disposition' =>
322
- 'attachment; filename=' + keyFile
365
+ 'attachment; filename=' + keyID
323
366
  }
324
367
  puts @header if @debug
325
- @body << File.open(keyFile,'r').read
368
+ @body << `gpg2 --homedir #{@keyDir} --armor --export #{keyID}`
326
369
  else
327
370
  #
328
371
  # return the key data for a human to read
329
372
  #
330
- keyID = File.basename(keyFile, '.*')
331
373
  @body << @headerHtml
332
374
  @body << @lookupForm
333
375
  @body << '<h1 class="simpleHKP-keyAsckeyId">Key: '+keyID+'</h1>'
334
376
  @body << '<h2 class="simpleHKP-keyDataTitle">Key data:</h2>'
335
377
  @body << '<pre class="simpleHKP-keyData">'
336
- @body << @keyLookupData[keyFile].gsub(/\\x3a/,':')
378
+ @body << @keyLookupData[keyID]['humanData']
337
379
  @body << '</pre>'
338
380
  @body << '<h2 class="simpleHKP-keyAscTitle">Key contents:</h2>'
339
381
  @body << '<pre class="simpleHKP-keyAsc">'
340
- @body << File.open(keyFile,'r').read
382
+ @body << `gpg2 --homedir #{@keyDir} --armor --export #{keyID}`
341
383
  @body << '</pre>'
342
384
  @body << @footer
343
385
  end
344
386
  end
345
387
 
346
- def extractKeyInfo(keyFile)
347
- keyInfo = Array.new
348
- return keyInfo unless @keyLookupData.has_key?(keyFile)
349
- #
350
- # extract the detailed key information from the PUBLIC key
351
- # see the doc/DETAILS file in the gnupg2 source code
352
- #
353
- @keyLookupData[keyFile].each_line do | aLine |
354
- next unless aLine =~ /^pub/
355
- keyData = aLine.split(/:/)
356
- keyInfo.push(keyData[9].gsub(/\\x3a/,':')) # -1 = user ID
357
- keyInfo.push(keyData[4]) # 0 = key ID
358
- keyInfo.push(keyData[3]) # 1 = key type (algorithm)
359
- keyInfo.push(keyData[2]) # 2 = key length
360
- keyInfo.push(keyData[5]) # 3 = creation date
361
- keyInfo.push(keyData[6]) # 4 = expiration date
362
- keyInfo.push(keyData[1]) # 5 = flags
363
- end
364
-
365
- keyInfo
366
- end
367
-
368
388
  def indexKeys(queryString)
369
389
  return replyBadRequest("No search field in queryString") unless
370
390
  queryString.has_key?('search')
@@ -377,13 +397,13 @@ class SimpleHKP
377
397
  Regexp::IGNORECASE | Regexp::MULTILINE)
378
398
  puts searchRegexp if @debug
379
399
  #
380
- # accumulate the keyFiles of any keys that match the serach
400
+ # accumulate the keyIDs of any keys that match the serach
381
401
  #
382
402
  keys = Array.new
383
- @keyLookupData.each_pair do | aKeyFile, keyData |
384
- next unless keyData.gsub(/\\x3a/,':') =~ searchRegexp
385
- puts "FOUND #{aKeyFile} (#{keyData})" if @debug
386
- keys.push(aKeyFile)
403
+ @keyLookupData.each_pair do | key, keyData |
404
+ next unless keyData['colonData'] =~ searchRegexp
405
+ puts "FOUND #{key} (#{keyData})" if @debug
406
+ keys.push(key)
387
407
  end
388
408
 
389
409
  if queryString.has_key?('options') &&
@@ -393,15 +413,24 @@ class SimpleHKP
393
413
  #
394
414
  @header = { 'Content-Type' => 'text/plain' }
395
415
  @body << "info:1:#{keys.size}\n"
396
- keys.each do | aKeyFile |
397
- keyInfo = extractKeyInfo(aKeyFile)
398
- next if keyInfo.empty?
399
- userID = keyInfo.shift
400
- @body << "pub:#{keyInfo.join(':')}\n"
401
- keyID = keyInfo.shift
402
- keyType = keyInfo.shift
403
- keyLength = keyInfo.shift
404
- @body << "uid:#{userID}:#{keyInfo.join(':')}\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(':')
405
434
  @body << "\n"
406
435
  end
407
436
  else
@@ -411,17 +440,11 @@ class SimpleHKP
411
440
  @body << @headerHtml
412
441
  @body << @lookupForm
413
442
  @body << '<h1 class="simpleHKP-searchString">Keys matching: ['+searchString+']</h1><ul class="simpleHKP-searchList">'
414
- keys.each do | aKeyFile |
415
- keyInfo = extractKeyInfo(aKeyFile)
416
- userID = keyInfo.shift
417
- keyID = keyInfo.shift
418
- keyType = keyInfo.shift
419
- keyLength = keyInfo.shift
420
- created = keyInfo.shift
421
- expires = keyInfo.shift
422
- flags = keyInfo.shift
423
- keyStr = " <li class=\"simpleHKP-searchItem\"><a href=\"lookup?op=get&search=#{keyID}\">#{keyID}:&nbsp;"
424
- keyStr << userID
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']
425
448
  keyStr << "</a></li>\n"
426
449
  @body << keyStr
427
450
  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.1.0
4
+ version: 1.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors: