diff options
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  | 
