diff options
author | azul <azul@riseup.net> | 2016-06-15 14:22:08 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-06-15 14:22:08 +0200 |
commit | 3c6dc3d7b902b46b82a3d8cd38bd3fa896024beb (patch) | |
tree | 559b3998465248138b6f24a76b6b3e55e60f3986 /lib/nickserver/hkp | |
parent | 5cad637a4a2a3de6b95ff1204fc29174e18b3124 (diff) | |
parent | 93258bd6fe6247e7af67f423243eba9808e920ee (diff) |
Merge pull request #3 from azul/refactor/transport-adapters
Refactor em specifics into http adapter
Diffstat (limited to 'lib/nickserver/hkp')
-rw-r--r-- | lib/nickserver/hkp/client.rb | 44 | ||||
-rw-r--r-- | lib/nickserver/hkp/fetch_key.rb | 56 | ||||
-rw-r--r-- | lib/nickserver/hkp/fetch_key_info.rb | 30 | ||||
-rw-r--r-- | lib/nickserver/hkp/key_info.rb | 9 | ||||
-rw-r--r-- | lib/nickserver/hkp/parse_key_info.rb | 26 | ||||
-rw-r--r-- | lib/nickserver/hkp/response.rb | 18 | ||||
-rw-r--r-- | lib/nickserver/hkp/source.rb | 66 | ||||
-rw-r--r-- | lib/nickserver/hkp/v_index_response.rb | 96 |
8 files changed, 246 insertions, 99 deletions
diff --git a/lib/nickserver/hkp/client.rb b/lib/nickserver/hkp/client.rb new file mode 100644 index 0000000..6bd239d --- /dev/null +++ b/lib/nickserver/hkp/client.rb @@ -0,0 +1,44 @@ +require 'nickserver/hkp' + +# +# Client for the HKP protocol. +# +# This is not a complete implementation - only the parts we need. +# Instantiate with an adapter that will take care of the http requests. +# +# For each request we yield http_status and the response content just +# like the adapter does. + + +module Nickserver; module Hkp + class Client + + def initialize(adapter) + @adapter = adapter + end + + # + # used to fetch an array of KeyInfo objects that match the given email + # + def get_key_infos_by_email(email, &block) + get op: 'vindex', search: email, fingerprint: 'on', &block + end + + # + # fetches ascii armored OpenPGP public key from the keyserver + # + def get_key_by_fingerprint(fingerprint, &block) + get op: 'get', search: "0x" + fingerprint, &block + end + + protected + + attr_reader :adapter + + def get(query, &block) + # in practice, exact=on seems to have no effect + query = {exact: 'on', options: 'mr'}.merge query + adapter.get Config.hkp_url, query: query, &block + end + end +end; end diff --git a/lib/nickserver/hkp/fetch_key.rb b/lib/nickserver/hkp/fetch_key.rb deleted file mode 100644 index 44621d3..0000000 --- a/lib/nickserver/hkp/fetch_key.rb +++ /dev/null @@ -1,56 +0,0 @@ -require 'em-http' - -# -# Fetch keys via HKP -# http://tools.ietf.org/html/draft-shaw-openpgp-hkp-00 -# - -module Nickserver; module HKP - - class FetchKey - include EM::Deferrable - - def get(uid) - FetchKeyInfo.new.search(uid).callback {|key_info_list| - best = pick_best_key(key_info_list) - get_key_by_fingerprint(best.keyid) {|key| - self.succeed key - } - }.errback {|status, msg| - self.fail status, msg - } - self - end - - # - # fetches ascii armored OpenPGP public key from the keyserver - # - def get_key_by_fingerprint(key_id) - params = {op: 'get', search: "0x" + key_id, exact: 'on', options: 'mr'} - http = EventMachine::HttpRequest.new(Config.hkp_url).get(query: params) - http.callback { - if http.response_header.status != 200 - self.fail http.response_header.status, "HKP Request failed" - else - yield http.response - end - } - http.errback { - self.fail 500, http.error - } - end - - protected - - # - # for now, just pick the newest key. - # - # in the future, we should perhaps pick the newest key - # that is signed by the oldest key. - # - def pick_best_key(key_info_list) - key_info_list.sort {|a,b| a.creationdate <=> b.creationdate}.last - end - end - -end; end
\ No newline at end of file diff --git a/lib/nickserver/hkp/fetch_key_info.rb b/lib/nickserver/hkp/fetch_key_info.rb deleted file mode 100644 index 2448bb1..0000000 --- a/lib/nickserver/hkp/fetch_key_info.rb +++ /dev/null @@ -1,30 +0,0 @@ -require 'em-http' - -# -# used to fetch an array of KeyInfo objects that match the given uid. -# - -module Nickserver; module HKP - class FetchKeyInfo - include EM::Deferrable - - def search(uid) - # in practice, exact=on seems to have no effect - params = {op: 'vindex', search: uid, exact: 'on', options: 'mr', fingerprint: 'on'} - EventMachine::HttpRequest.new(Config.hkp_url).get(query: params).callback {|http| - parser = ParseKeyInfo.new http.response_header, http.response - keys = parser.keys(uid) - if keys.any? - self.succeed keys - else - self.fail parser.status(uid), parser.msg(uid) - end - }.errback {|http| - self.fail 500, http.error - } - self - end - - end - -end; end diff --git a/lib/nickserver/hkp/key_info.rb b/lib/nickserver/hkp/key_info.rb index adb75d8..d4ecf10 100644 --- a/lib/nickserver/hkp/key_info.rb +++ b/lib/nickserver/hkp/key_info.rb @@ -1,4 +1,5 @@ require 'cgi' +require 'nickserver/hkp' # # Class to represent the key information result from a query to a key server @@ -9,9 +10,9 @@ require 'cgi' # format definition of machine readable index output is here: # http://tools.ietf.org/html/draft-shaw-openpgp-hkp-00#section-5.2 # -module Nickserver; module HKP +module Nickserver::Hkp class KeyInfo - attr_accessor :uids, :keyid, :algo, :keylen, :creationdate, :expirationdate, :flags + attr_accessor :uids, :keyid, :algo, :flags def initialize(hkp_record) uid_lines = hkp_record.split("\n") @@ -19,7 +20,7 @@ module Nickserver; module HKP @keyid, @algo, @keylen_s, @creationdate_s, @expirationdate_s, @flags = pub_line.split(':')[1..-1] @uids = [] uid_lines.each do |uid_line| - uid, creationdate, expirationdate, flags = uid_line.split(':')[1..-1] + uid, _creationdate, _expirationdate, _flags = uid_line.split(':')[1..-1] # for now, ignore the expirationdate and flags of uids. sks does return them anyway @uids << CGI.unescape(uid.sub(/.*<(.+)>.*/, '\1')) end @@ -66,4 +67,4 @@ module Nickserver; module HKP end end -end; end +end diff --git a/lib/nickserver/hkp/parse_key_info.rb b/lib/nickserver/hkp/parse_key_info.rb index 8934829..9d59d6b 100644 --- a/lib/nickserver/hkp/parse_key_info.rb +++ b/lib/nickserver/hkp/parse_key_info.rb @@ -1,29 +1,37 @@ # -# Simple parser for HKP KeyInfo responses. +# Simple parser for Hkp KeyInfo responses. # # Focus is on simple here. Trying to avoid state and sideeffects. # Parsing a response with 12 keys and validating them takes 2ms. # So no need for memoization and making things more complex. # -module Nickserver; module HKP +module Nickserver; module Hkp class ParseKeyInfo # for this regexp to work, the source text must end in a trailing "\n", # which the output of sks does. MATCH_PUB_KEY = /(^pub:.+?\n(^uid:.+?\n)+)/m - # header -- header of the hkp response + # status -- http status of the hkp response # vindex_result -- raw output from a vindex hkp query (machine readable) - def initialize(header, vindex_result) - @header = header + def initialize(status, vindex_result) + @status = status @vindex_result = vindex_result end - def status(uid) + def status_for(uid) if hkp_ok? && keys(uid).empty? error_status(uid) else - header.status + status + end + end + + def response_for(uid) + if keys(uid).any? + keys(uid) + else + msg(uid) end end @@ -41,7 +49,7 @@ module Nickserver; module HKP protected - attr_reader :header + attr_reader :status attr_reader :vindex_result def error_status(uid) @@ -78,7 +86,7 @@ module Nickserver; module HKP end def hkp_ok? - header.status == 200 + status == 200 end def error_message(uid, key, err) diff --git a/lib/nickserver/hkp/response.rb b/lib/nickserver/hkp/response.rb new file mode 100644 index 0000000..c52e25f --- /dev/null +++ b/lib/nickserver/hkp/response.rb @@ -0,0 +1,18 @@ +module Nickserver::Hkp + class Response + + attr_reader :status, :content + + def initialize(uid, key) + @content = format_response(address: uid, openpgp: key) + @status = 200 + end + + protected + + def format_response(map) + map.to_json + end + + end +end diff --git a/lib/nickserver/hkp/source.rb b/lib/nickserver/hkp/source.rb new file mode 100644 index 0000000..cae3e01 --- /dev/null +++ b/lib/nickserver/hkp/source.rb @@ -0,0 +1,66 @@ +require 'nickserver/response' +require 'nickserver/hkp/response' +require 'nickserver/hkp/client' +require "nickserver/hkp/parse_key_info" +require "nickserver/hkp/key_info" + + +# +# Fetch keys via HKP +# http://tools.ietf.org/html/draft-shaw-openpgp-hkp-00 +# + +module Nickserver; module Hkp + class Source + + def initialize(adapter) + @adapter = adapter + end + + def query(nick, &block) + search(nick) do |status, response| + if status == 200 + best = pick_best_key(response) + get_key_by_fingerprint(nick, best.keyid, &block) + else + yield Nickserver::Response.new(status, response) + end + end + end + + def search(nick, &block) + client.get_key_infos_by_email(nick) do |status, response| + parser = ParseKeyInfo.new status, response + yield parser.status_for(nick), parser.response_for(nick) + end + end + + protected + + attr_reader :adapter + + # + # for now, just pick the newest key. + # + # in the future, we should perhaps pick the newest key + # that is signed by the oldest key. + # + def pick_best_key(key_info_list) + key_info_list.sort {|a,b| a.creationdate <=> b.creationdate}.last + end + + def get_key_by_fingerprint(nick, fingerprint) + client.get_key_by_fingerprint fingerprint do |status, response| + if status == 200 + yield Response.new nick, response + else + yield Nickserver::Response.new status, "HKP Request failed" + end + end + end + + def client + @client ||= Client.new(adapter) + end + end +end; end diff --git a/lib/nickserver/hkp/v_index_response.rb b/lib/nickserver/hkp/v_index_response.rb new file mode 100644 index 0000000..865d476 --- /dev/null +++ b/lib/nickserver/hkp/v_index_response.rb @@ -0,0 +1,96 @@ +require 'nickserver/hkp' +require 'nickserver/hkp/key_info' + +# +# Simple parser for Hkp KeyInfo responses. +# +# Focus is on simple here. Trying to avoid state and sideeffects. +# Parsing a response with 12 keys and validating them takes 2ms. +# So no need for memoization and making things more complex. +module Nickserver::Hkp + class VIndexResponse + + # for this regexp to work, the source text must end in a trailing "\n", + # which the output of sks does. + MATCH_PUB_KEY = /(^pub:.+?\n(^uid:.+?\n)+)/m + + # hkp_response -- raw output from a vindex hkp query (machine readable) + def initialize(nick, hkp_response) + @nick = nick.to_s + @vindex_result = hkp_response[:body] + end + + def status + if keys.empty? + error_status + else + 200 + end + end + + def keys + key_infos.reject { |key| error_for_key(key) } + end + + def msg + if errors.any? + error_messages.join "\n" + else + "Could not fetch keyinfo." + end + end + + protected + + attr_reader :vindex_result, :nick + + def error_status + if errors.any? + 500 + else + 404 + end + end + + def errors + key_infos.map{|key| error_for_key(key) }.compact + end + + def error_messages + key_infos.map do |key| + err = error_for_key(key) + error_message(key, err) + end.compact + end + + def key_infos + all_key_infos.select do |key_info| + key_info.uids.include?(nick) + end + end + + def all_key_infos + @all_key_infos ||= vindex_result.scan(MATCH_PUB_KEY).map do |match| + KeyInfo.new(match[0]) + end + end + + def error_message(key, err) + "Ignoring key #{key.keyid} for #{nick}: #{err}" if err + end + + def error_for_key(key) + if key.keylen < 2048 + "key length is too short." + elsif key.expired? + "key expired." + elsif key.revoked? + "key revoked." + elsif key.disabled? + "key disabled." + elsif key.expirationdate && key.expirationdate < Time.now + "key expired" + end + end + end +end |