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/.gitignore CHANGED
@@ -1,6 +1,10 @@
1
+ _site
2
+ .sass-cache
1
3
  Manifest.txt
2
4
  *.gem
3
5
  /pkg/
4
6
  /tmp/
5
7
  /rdoc/
6
-
8
+ /_site/
9
+ /.sass*/
10
+ pkg/
data/History.txt CHANGED
@@ -3,5 +3,3 @@
3
3
  * 1 major enhancement
4
4
 
5
5
  * Birthday!
6
-
7
-
data/Manifest.txt CHANGED
@@ -4,4 +4,7 @@ History.txt
4
4
  README.rdoc
5
5
  Rakefile
6
6
  lib/simpleHKP.rb
7
- lib/simpleHKP/echo.rb
7
+ lib/simpleHKP/echo.rb
8
+ lib/simpleHKP/identities.rb
9
+ lib/simpleHKP/keys.rb
10
+ lib/simpleHKP/server.rb
data/README.rdoc CHANGED
@@ -1,32 +1,22 @@
1
1
  = rGem-simple-gnupg-keyserver
2
2
 
3
- home :: https://github.com/stephengaito/rGem-simple-gnupg-keyserver
3
+ code :: https://github.com/stephengaito/rGem-simple-gnupg-keyserver
4
4
 
5
- == DESCRIPTION:
6
-
7
- SimpleHKP is a simple Ruby/rack based GnuPG HKP key server.
8
-
9
- Its sole task is to supply a limited number of GnuPG _public_ keys
10
- to/from MonkeySphere on a limited number of machines/servers. It is
11
- _not_ meant to server hundreds of keys, so it is not meant as a truely
12
- public key server.
13
-
14
- If you need to run your own _large_ scale production Key Sever then use
15
- {SKS}[https://bitbucket.org/skskeyserver/sks-keyserver/wiki/Home]. You
16
- might want to read Paul Bauer's {How To Setup A Free PGP Key Server in
17
- Ubuntu}[http://www.bauer-power.net/2010/05/how-to-setup-free-pgp-key-server-in.html].
5
+ docs :: https://stephengaito.github.io/rGem-simple-gnupg-keyserver
18
6
 
19
- === Key storage
7
+ == DESCRIPTION:
20
8
 
21
- Since this is a _simple_ key server, all keys are imported into a GnuPG
22
- keyring located in the "keys" directory.
9
+ SimpleHKP is a simple Ruby/rack based GnuPG extendedHKP key and
10
+ identity server packaged as a standard Ruby Gem.
23
11
 
24
- === Key search
12
+ As such it conforms to an {extended
13
+ version}[http://stephengaito.github.io/rGem-simple-gnupg-keyserver/] of
14
+ {the OpenPGP HTTP Keyserver Protocol (HKP)
15
+ (draft-shaw-openpgp-hkp-00.txt)}[http://tools.ietf.org/html/draft-shaw-openpgp-hkp-00].
25
16
 
26
- For each uploaded key, the key's "gpg2 --with-fingerprint --with-colon"
27
- output (key data) is stored in an internal ruby Hash using the key's
28
- keyID name as hash key. All searches become Ruby regular expressions
29
- which are matched against each key's key data.
17
+ The {associated
18
+ documentation}[https://stephengaito.github.io/rGem-simple-gnupg-keyserver]
19
+ provides more detail.
30
20
 
31
21
  === Security:
32
22
 
@@ -47,6 +37,13 @@ This rack application is intentionally as self contained and simple as
47
37
  possible. If you have any security concerns, this rack application is
48
38
  easily readable.
49
39
 
40
+ When used with SimpleMonkey, this SimpleHKP acts like a distributed
41
+ password file. All important (non-public identity) details are
42
+ encrypted, however, the collection of servers, users and their
43
+ associated roles are visibly in the "public domain". It is important to
44
+ run your own collection of SimpleHKP servers *inside* a controlled
45
+ domain or VPN.
46
+
50
47
  == SYNOPSIS:
51
48
 
52
49
  A typical rackup.ru file might be:
@@ -68,13 +65,14 @@ key/value pairs.
68
65
  The current default options are:
69
66
 
70
67
  defaultOptions = {
71
- 'debug' => false, # should debug output to logged?
72
- 'title' => 'SimpleHKP' # the title used by the default headerHTML
73
- 'simpleHKPdir' => 'simpleHKP', # base disk path to simpleHKP disk space
74
- 'keyDir' => 'keys', # subdir to key storage directory
75
- 'mediaDir' => 'media', # subdir to any css, js, images etc
76
- 'htmlDir' => 'html', # subdir to html partials
77
- 'mimeMap' => { # a file ext to mime mapping
68
+ 'debug' => false, # should debug output to logged?
69
+ 'title' => 'SimpleHKP' # the title used by the default headerHTML
70
+ 'simpleHKPdir' => 'simpleHKP', # base disk path to simpleHKP disk space
71
+ 'keyDir' => 'keys', # subdir to key storage directory
72
+ 'idDir' => 'identities', # subdir to identity storage directory
73
+ 'mediaDir' => 'media', # subdir to any css, js, images etc
74
+ 'htmlDir' => 'html', # subdir to html partials
75
+ 'mimeMap' => { # a file ext to mime mapping
78
76
  'css' => 'text/css',
79
77
  'html' => 'text/html',
80
78
  'js' => 'text/javascript'
@@ -86,14 +84,16 @@ the use of humans:
86
84
 
87
85
  * header.html
88
86
  * defaultBody.html
89
- * lookupForm.html
90
- * uploadForm.html
87
+ * lookupKeysForm.html
88
+ * lookupIdentitiesForm.html
89
+ * uploadKeyForm.html
90
+ * uploadIdentityForm.html
91
91
  * footer.html
92
92
 
93
93
  == SYNCHRONIZATION
94
94
 
95
- A 'simpleHKP/echo' class has been added which knows how to echo keys from
96
- one key server to another.
95
+ A 'simpleHKP/echo' class has been added which knows how to echo
96
+ keys/identities from one key server to another.
97
97
 
98
98
  So that for example the following ruby script could be placed into one
99
99
  of your machine's /etc/daily directories and the script would ensure
@@ -101,13 +101,13 @@ all key servers are synchronized daily.
101
101
 
102
102
  #!/usr/bin/env ruby
103
103
 
104
- # A simple key server sychronization example
104
+ # A simple key/identity server sychronization example
105
105
  #
106
106
  # We use a hub-spoke model. We choose one "hub" key server to act as
107
- # the master key server and amalgamate all of the keys from the spoke
108
- # key server back to the hub key server. We then push the amalgamated
109
- # keys in the hub back to the spokes, so that after two passes, all key
110
- # servers have the same keys.
107
+ # the master key server and amalgamate all of the keys/identities from
108
+ # the spoke key server back to the hub key server. We then push the
109
+ # amalgamated keys/identities in the hub back to the spokes, so that
110
+ # after two passes, all key servers have the same keys/identities.
111
111
 
112
112
  require 'simpleHKP/echo'
113
113
 
@@ -118,13 +118,13 @@ all key servers are synchronized daily.
118
118
  'spokeKeyServer3'
119
119
  ]
120
120
 
121
- # Start by bringing all spoke keys into the hub
121
+ # Start by bringing all spoke keys/identities into the hub
122
122
  #
123
123
  spokeKeyServers.each do | aKeyServer |
124
124
  SimpleHKPEcho.echoFromTo(aKeyServer, hubKeyServer)
125
125
  end
126
126
 
127
- # now send the amalgamated hub keys back to each spoke
127
+ # now send the amalgamated hub keys/identities back to each spoke
128
128
  #
129
129
  spokeKeyServers.each do | aKeyServer |
130
130
  SimpleHKPEcho.echoFromTo(hubKeyServer, aKeyServer)
@@ -137,6 +137,10 @@ The SimpleHKPEcho.echoFromTo method fails gracefully if a given key
137
137
  server is offline, by simply returning. In the example above, all other
138
138
  echo pairs (from, to) will be tried.
139
139
 
140
+ To just echo either the keys or identities you can use
141
+ SimpleHKPEcho.echoKeysFromTo or SimpleHKPEcho.echoIdentitiesFromTo
142
+ respectively.
143
+
140
144
  == REQUIREMENTS:
141
145
 
142
146
  There are explicitly no external Ruby requirements other than Ruby and
@@ -175,3 +179,4 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
175
179
  TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
176
180
  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
177
181
 
182
+
data/Rakefile CHANGED
@@ -37,3 +37,4 @@ Hoe.spec 'simple-gnupg-keyserver' do
37
37
  end
38
38
 
39
39
  # vim: syntax=ruby
40
+
@@ -2,10 +2,11 @@ require 'uri'
2
2
  require 'net/http'
3
3
  require 'pp'
4
4
 
5
- # This code provides a simple way to ensure keys are synchronized
6
- # between a pair of key servers.
5
+ # This code provides a simple way to ensure keys/identities are
6
+ # synchronized between a pair of key/identity servers.
7
7
 
8
- # It conforms to: The OpenPGP HTTP Keyserver Protocol (HKP)
8
+ # It conforms to our simple extension to:
9
+ # The OpenPGP HTTP Keyserver Protocol (HKP)
9
10
  # draft-shaw-openpgp-hkp-00.txt
10
11
  # http://tools.ietf.org/html/draft-shaw-openpgp-hkp-00
11
12
 
@@ -48,7 +49,7 @@ class SimpleHKPEcho
48
49
  aKeyServerStr
49
50
  end
50
51
 
51
- def echoFromTo(fromKeyServer, toKeyServer, options = {})
52
+ def echoKeysFromTo(fromKeyServer, toKeyServer, options = {})
52
53
  begin
53
54
  debug = options.delete('debug')
54
55
  puts fromKeyServer if debug
@@ -59,7 +60,7 @@ class SimpleHKPEcho
59
60
  puts toKeyServer if debug
60
61
 
61
62
  keys = Array.new
62
- url = URI.parse(fromKeyServer+'/lookup?search=&op=index&options=mr')
63
+ url = URI.parse(fromKeyServer+'/pks/lookup?search=&op=index&options=mr')
63
64
  response = Net::HTTP.get_response(url)
64
65
  response.body.each_line do | aLine |
65
66
  next unless aLine =~ /^pub/
@@ -68,19 +69,59 @@ class SimpleHKPEcho
68
69
  pp keys if debug
69
70
  keys.each do | aKey |
70
71
  keyData = ""
71
- url = URI.parse(fromKeyServer+"/lookup?op=get&options=mr&search=#{aKey}")
72
+ url = URI.parse(fromKeyServer+"/pks/lookup?op=get&options=mr&search=#{aKey}")
72
73
  response = Net::HTTP.get_response(url)
73
74
  keyData = response.body
74
75
  puts aKey if debug
75
76
  puts keyData if debug
76
- url = URI.parse(toKeyServer+'/add')
77
+ url = URI.parse(toKeyServer+'/pks/add')
77
78
  Net::HTTP.post_form(url, { 'keytext' => keyData })
78
79
  end
79
- rescue SocketError => se
80
+ rescue Exception => ex
80
81
  puts "Cound not echo keys from #{fromKeyServer} to #{toKeyServer}"
82
+ puts ex
81
83
  end
82
84
  end
83
85
 
86
+ def echoIdentitiesFromTo(fromKeyServer, toKeyServer, options = {})
87
+ begin
88
+ debug = options.delete('debug')
89
+ puts fromKeyServer if debug
90
+ fromKeyServer = convertToHttp(fromKeyServer)
91
+ puts fromKeyServer if debug
92
+ puts toKeyServer if debug
93
+ toKeyServer = convertToHttp(toKeyServer)
94
+ puts toKeyServer if debug
95
+
96
+ identities = Array.new
97
+ url = URI.parse(fromKeyServer+'/pis/lookup?search=&op=index&options=mr')
98
+ response = Net::HTTP.get_response(url)
99
+ response.body.each_line do | aLine |
100
+ next unless aLine =~ /^idn/
101
+ identities.push(aLine.split(/:/)[1])
102
+ end
103
+ pp identities if debug
104
+ identities.each do | anIdFile |
105
+ idData = ""
106
+ url = URI.parse(fromKeyServer+"/pis/lookup?op=get&options=mr&search=#{anIdFile}")
107
+ response = Net::HTTP.get_response(url)
108
+ idData = response.body
109
+ puts anIdFile if debug
110
+ puts idData if debug
111
+ url = URI.parse(toKeyServer+'/pis/add')
112
+ Net::HTTP.post_form(url, { 'keytext' => idData })
113
+ end
114
+ rescue Exception => ex
115
+ puts "Cound not echo identities from #{fromKeyServer} to #{toKeyServer}"
116
+ puts ex
117
+ end
118
+ end
119
+
120
+ def echoFromTo(fromKeyServer, toKeyServer, options = {})
121
+ echoKeysFromTo(fromKeyServer, toKeyServer, options)
122
+ echoIdentitiesFromTo(fromKeyServer, toKeyServer, options)
123
+ end
124
+
84
125
  end
85
126
 
86
127
  end
@@ -0,0 +1,240 @@
1
+
2
+ module SimpleHKP
3
+
4
+ module Identities
5
+
6
+ def loadIdentityFile(fileName)
7
+ identityContents = ''
8
+ begin
9
+ identityContents = File.open(fileName, 'r').read
10
+ rescue Exception => e
11
+ puts "Error reading file #{fileName}: #{e.message}"
12
+ end
13
+ identityContents
14
+ end
15
+
16
+ def getIdentityMetaData(fileContents)
17
+ metaData = Hash.new
18
+ begin
19
+ if fileContents =~ /\A(---\s*\n.*?\n?)^((---|\.\.\.)\s*$\n?)/m
20
+ metaData = SafeYAML.load($1)
21
+ end
22
+ rescue SyntaxError => e
23
+ puts "YAML Exception reading #{fileName}: #{e.message}"
24
+ end
25
+
26
+ if !metaData.has_key?('role') ||
27
+ !metaData.has_key?('userKeyID') ||
28
+ metaData['userKeyID'] !~ /^\h+$/ then
29
+ puts "WARNING the identity file has NO role or userKeyID or a malformed userKeyID"
30
+ return ''
31
+ end
32
+
33
+ #
34
+ # normalize the meta data with any missing (but useful) values
35
+ #
36
+ metaData['userID'] = metaData['userKeyID'] unless
37
+ metaData.has_key?('userID')
38
+ metaData['algorithm'] = 'unknown' unless metaData.has_key?('algorithm')
39
+ metaData['created'] = Date.today.strftime("%Y%m%d") unless
40
+ metaData.has_key?('created')
41
+ metaData['expires'] = Date.today.strftime("%Y%m%d") unless
42
+ metaData.has_key?('expires')
43
+ metaData['flags'] = '' unless metaData.has_key?('flags')
44
+ #
45
+ # compute the idFile for searching
46
+ #
47
+ metaData['identity'] = metaData['role']+'-'+metaData['userKeyID']
48
+ #
49
+ # wrap the whole meta data into a searchable string
50
+ #
51
+ metaData['searchData'] = metaData.to_s
52
+ metaData
53
+ end
54
+
55
+ def indexIdentityFiles
56
+ puts "SimpleHKP: indexing identities" if @debug
57
+ @idMetaData = Hash.new
58
+ Dir.glob(@idDir+'/**/*.asc') do | aFile |
59
+ idFile = File.basename(aFile, '.*')
60
+ puts aFile if @debug
61
+ puts idFile if @debug
62
+ identityFile = loadIdentityFile(aFile)
63
+ metaData = getIdentityMetaData(identityFile)
64
+ if metaData.empty? then
65
+ puts "WARNING the identity file [#{aFile}] is malformed!"
66
+ else
67
+ @idMetaData[idFile] = metaData
68
+ end
69
+ puts @idMetaData[idFile] if @debug
70
+ end
71
+ # File.open(@idIndex, 'w') do | indexFile |
72
+ # indexFile.puts "# identity metaData index created: #{DateTime.now}"
73
+ # indexFile.write(YAML.dump(@idMetaData))
74
+ # end
75
+ puts "SimpleHKP: finished indexing identities" if @debug
76
+ end
77
+
78
+ def loadIdentities
79
+ @idLookupData = Hash.new
80
+ puts "SimpleHKP: loading identities" if @debug
81
+ indexIdentityFiles
82
+ pp @idMetaData
83
+ puts "SimpleHKP: finished loading identities" if @debug
84
+ end
85
+
86
+ def storeIdentity(env)
87
+ #
88
+ # decode the post data
89
+ #
90
+ keys = URI.decode_www_form(env['rack.input'].read)
91
+ #
92
+ # ensure we are being sent a key
93
+ #
94
+ return replyBadRequest("No keytext field in post data") unless
95
+ keys[0][0] =~ /keytext/
96
+ pp keys[0][1] if @debug
97
+ metaData = getIdentityMetaData(keys[0][1])
98
+ pp metaData
99
+ return replyBadRequest("malformed identity metaData") if metaData.empty?
100
+ #
101
+ # store this key as a flat file with the name of the keyID.asc
102
+ #
103
+ idFile = metaData['role']+'-'+metaData['userKeyID']
104
+ idFileName = @idDir+'/'+idFile+'.asc'
105
+ idChanged = 'unchanged'
106
+ if !File.exists?(idFileName) || keys[0][1] != File.read(idFileName) then
107
+ idChanged = 'changed'
108
+ File.write(idFileName, keys[0][1])
109
+ @idMetaData[idFile] = metaData
110
+ end
111
+ pp @idMetaData
112
+ #
113
+ # return OK
114
+ #
115
+ @statusCode = 200
116
+ @body << @headerHtml
117
+ @body << "<h1 class=\"simpleHKP-storedIdentity\">Stored identity: [#{idFile}]</h1>"
118
+ @body << "<p>identity #{idChanged}</p>"
119
+ @body << "<h2 class=\"simpleHKP-identityDataID\">Identity data for identity: [#{idFile}]</h2>"
120
+ @body << '<pre class="simpleHKP-identityData">'+keys[0][1]+"</pre>\n"
121
+ @body << @footer
122
+ end
123
+
124
+ def lookUpIdentity(queryString)
125
+ return replyBadRequest("No search field in queryString") unless
126
+ queryString.has_key?('search')
127
+ #
128
+ # normalize the search string
129
+ #
130
+ searchString = queryString['search']
131
+ searchRegexp = Regexp.new(searchString,
132
+ Regexp::IGNORECASE | Regexp::MULTILINE)
133
+ puts searchRegexp if @debug
134
+ #
135
+ # (linearly) look through the hash of known identities
136
+ # looking for the FIRST match
137
+ #
138
+ idFile = nil
139
+ pp @idMetaData
140
+ @idMetaData.each_pair do | idKey, idData |
141
+ next unless idData['searchData'] =~ searchRegexp
142
+ puts "FOUND #{idKey} (#{idData})" if @debug
143
+ idFile = idKey
144
+ break
145
+ end
146
+ return replyNotFound if idFile.nil?
147
+
148
+ idFileName = @idDir+'/'+idFile+'.asc'
149
+ if queryString.has_key?('options') &&
150
+ queryString['options'] == 'mr' then
151
+ #
152
+ # return the key data in machine readable format
153
+ #
154
+ @header = {
155
+ 'Content-Type' => 'text/plain; charset=utf-8',
156
+ 'Content-Disposition' =>
157
+ 'attachment; filename=' + File.basename(idFileName)
158
+ }
159
+ puts @header if @debug
160
+ @body << File.read(idFileName)
161
+ else
162
+ #
163
+ # return the key data for a human to read
164
+ #
165
+ @body << @headerHtml
166
+ @body << @lookupIdentitiesForm
167
+ @body << '<h1 class="simpleHKP-identityAscIdFile">Identity: '+idFile+'</h1>'
168
+ @body << '<h2 class="simpleHKP-identityAscTitle">Identity contents:</h2>'
169
+ @body << '<pre class="simpleHKP-identityAsc">'
170
+ @body << File.read(idFileName)
171
+ @body << '</pre>'
172
+ @body << @footer
173
+ end
174
+ end
175
+
176
+ def indexIdentities(queryString)
177
+ return replyBadRequest("No search field in queryString") unless
178
+ queryString.has_key?('search')
179
+ #
180
+ # normalize the search string
181
+ #
182
+ searchString = queryString['search']
183
+ searchRegexp = Regexp.new(searchString,
184
+ Regexp::IGNORECASE | Regexp::MULTILINE)
185
+ puts searchRegexp if @debug
186
+ #
187
+ # accumulate the idFiles of any identities that match the search
188
+ #
189
+ idFiles = Array.new
190
+ pp @idMetaData
191
+ @idMetaData.each_pair do | idKey, idData |
192
+ next unless idData['searchData'] =~ searchRegexp
193
+ puts "FOUND #{idKey} (#{idData})" if @debug
194
+ idFiles.push(idKey)
195
+ end
196
+
197
+ if queryString.has_key?('options') &&
198
+ queryString['options'] == 'mr' then
199
+ #
200
+ # return a machine readable list of the dFiles found
201
+ #
202
+ @header = { 'Content-Type' => 'text/plain' }
203
+ @body << "info:1:#{idFiles.size}\n"
204
+ idFiles.each do | anIdFile |
205
+ next unless @idMetaData.has_key?(anIdFile)
206
+ idData = @idMetaData[anIdFile]
207
+ idnData = [ "idn" ]
208
+ idnData << anIdFile
209
+ idnData << idData['role']
210
+ idnData << idData['userID'].gsub(/:/, '\x3a')
211
+ idnData << idData['algorithm']
212
+ idnData << idData['created']
213
+ idnData << idData['expires']
214
+ idnData << idData['flags']
215
+ @body << idnData.join(':')
216
+ @body << "\n"
217
+ end
218
+ else
219
+ #
220
+ # return a (simple) human readable list of the keys found
221
+ #
222
+ @body << @headerHtml
223
+ @body << @lookupIdentitiesForm
224
+ @body << '<h1 class="simpleHKP-searchString">Identities matching: ['+searchString+']</h1><ul class="simpleHKP-searchList">'
225
+ idFiles.each do | anIdFile |
226
+ next unless @idMetaData.has_key?(anIdFile)
227
+ idData = @idMetaData[anIdFile]
228
+ idStr = " <li class=\"simpleHKP-searchItem\"><a href=\"/pis/lookup?op=get&search=#{anIdFile}\">"
229
+ idStr << anIdFile+':&nbsp;'+idData['userID']+'&nbsp;('+idData['role']+')'
230
+ idStr << "</a></li>\n"
231
+ @body << idStr
232
+ end
233
+ @body << '</ul>'
234
+ @body << @footer
235
+ end
236
+ pp @body if @debug
237
+ end
238
+
239
+ end
240
+ end