summaryrefslogtreecommitdiff
path: root/lib/nickserver/hkp
diff options
context:
space:
mode:
authorazul <azul@riseup.net>2016-06-15 14:22:08 +0200
committerGitHub <noreply@github.com>2016-06-15 14:22:08 +0200
commit3c6dc3d7b902b46b82a3d8cd38bd3fa896024beb (patch)
tree559b3998465248138b6f24a76b6b3e55e60f3986 /lib/nickserver/hkp
parent5cad637a4a2a3de6b95ff1204fc29174e18b3124 (diff)
parent93258bd6fe6247e7af67f423243eba9808e920ee (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.rb44
-rw-r--r--lib/nickserver/hkp/fetch_key.rb56
-rw-r--r--lib/nickserver/hkp/fetch_key_info.rb30
-rw-r--r--lib/nickserver/hkp/key_info.rb9
-rw-r--r--lib/nickserver/hkp/parse_key_info.rb26
-rw-r--r--lib/nickserver/hkp/response.rb18
-rw-r--r--lib/nickserver/hkp/source.rb66
-rw-r--r--lib/nickserver/hkp/v_index_response.rb96
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