simple-gnupg-keyserver 1.1.0 → 1.2.0

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