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.
- data/README.rdoc +4 -3
- data/lib/simpleHKP.rb +102 -79
- metadata +1 -1
data/README.rdoc
CHANGED
@@ -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
|
22
|
-
|
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
|
-
|
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
|
data/lib/simpleHKP.rb
CHANGED
@@ -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.
|
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
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
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
|
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
|
312
|
+
keyID = nil
|
260
313
|
gpgKeyData.each_line do | aLine |
|
261
314
|
keyData = aLine.split(/:/)
|
262
315
|
next unless keyData[0] =~ /pub/
|
263
|
-
keyID
|
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">'+
|
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
|
-
|
306
|
-
@keyLookupData.each_pair do |
|
307
|
-
next unless keyData =~ searchRegexp
|
308
|
-
puts "FOUND #{
|
309
|
-
|
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
|
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=' +
|
365
|
+
'attachment; filename=' + keyID
|
323
366
|
}
|
324
367
|
puts @header if @debug
|
325
|
-
@body <<
|
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[
|
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 <<
|
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
|
400
|
+
# accumulate the keyIDs of any keys that match the serach
|
381
401
|
#
|
382
402
|
keys = Array.new
|
383
|
-
@keyLookupData.each_pair do |
|
384
|
-
next unless keyData
|
385
|
-
puts "FOUND #{
|
386
|
-
keys.push(
|
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 |
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
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 |
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
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}: "
|
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}: "
|
447
|
+
keyStr << keyData['userID']
|
425
448
|
keyStr << "</a></li>\n"
|
426
449
|
@body << keyStr
|
427
450
|
end
|