From 457c2bf135be68b3c49ff20d3f23a6f8507aeda5 Mon Sep 17 00:00:00 2001 From: elijah Date: Mon, 24 Dec 2012 02:02:24 -0800 Subject: initial commit --- lib/nickserver.rb | 10 ++++++ lib/nickserver/config.rb | 14 ++++++++ lib/nickserver/hkp/fetch_key.rb | 51 ++++++++++++++++++++++++++ lib/nickserver/hkp/fetch_key_info.rb | 66 ++++++++++++++++++++++++++++++++++ lib/nickserver/hkp/key_info.rb | 69 +++++++++++++++++++++++++++++++++++ lib/nickserver/server.rb | 70 ++++++++++++++++++++++++++++++++++++ lib/nickserver/version.rb | 3 ++ 7 files changed, 283 insertions(+) create mode 100644 lib/nickserver.rb create mode 100644 lib/nickserver/config.rb create mode 100644 lib/nickserver/hkp/fetch_key.rb create mode 100644 lib/nickserver/hkp/fetch_key_info.rb create mode 100644 lib/nickserver/hkp/key_info.rb create mode 100644 lib/nickserver/server.rb create mode 100644 lib/nickserver/version.rb (limited to 'lib') diff --git a/lib/nickserver.rb b/lib/nickserver.rb new file mode 100644 index 0000000..9ddc839 --- /dev/null +++ b/lib/nickserver.rb @@ -0,0 +1,10 @@ + +require "nickserver/version" +require "nickserver/config" + +require "nickserver/hkp/key_info" +require "nickserver/hkp/fetch_key_info" +require "nickserver/hkp/fetch_key" + +require "nickserver/server" + diff --git a/lib/nickserver/config.rb b/lib/nickserver/config.rb new file mode 100644 index 0000000..d47fc68 --- /dev/null +++ b/lib/nickserver/config.rb @@ -0,0 +1,14 @@ +module Nickserver + class Config + class << self + attr_accessor :sks_url + attr_accessor :port + end + end + + # + # set reasonable defaults + # + Config.sks_url = 'https://hkps.pool.sks-keyservers.net:/pks/lookup' + Config.port = 6425 # aka "NICK" +end diff --git a/lib/nickserver/hkp/fetch_key.rb b/lib/nickserver/hkp/fetch_key.rb new file mode 100644 index 0000000..c72ee11 --- /dev/null +++ b/lib/nickserver/hkp/fetch_key.rb @@ -0,0 +1,51 @@ +require 'em-http' + +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 {|msg| + self.fail 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.sks_url).get(:query => params) + http.callback { + if http.response_header.status != 200 + self.fail http.response_header.status #"Request failed with #{http.response_header.status}: #{http.response}" + else + yield http.response + end + } + http.errback { + self.fail 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 new file mode 100644 index 0000000..48ef48a --- /dev/null +++ b/lib/nickserver/hkp/fetch_key_info.rb @@ -0,0 +1,66 @@ +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 + + # 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 + + 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.sks_url).get(:query => params).callback {|http| + if http.response_header.status != 200 + self.fail http.response_header.status + else + self.succeed parse(uid, http.response) + end + }.errback {|http| + self.fail http.error + } + self + end + + # + # input: + # uid -- uid to search for + # vindex_result -- raw output from a vindex hkp query (machine readable) + # + # returns: + # an array of eligible keys (as HKPKeyInfo objects) matching uid. + # + # keys are eliminated from eligibility for a number of reasons, including expiration, + # revocation, uid match, key length, and so on... + # + def parse(uid, vindex_result) + keys = [] + now = Time.now + vindex_result.scan(MATCH_PUB_KEY).each do |match| + key_info = KeyInfo.new(match[0]) + if key_info.uids.include?(uid) + if key_info.keylen <= 1024 + #puts 'key length is too short' + elsif key_info.expired? + #puts 'ignoring expired key' + elsif key_info.revoked? + #puts 'ignoring revoked key' + elsif key_info.disabled? + #puts 'ignoring disabled key' + elsif key_info.expirationdate && key_info.expirationdate < now + #puts 'ignoring expired key' + else + keys << key_info + end + end + end + keys + end + end + +end; end \ No newline at end of file diff --git a/lib/nickserver/hkp/key_info.rb b/lib/nickserver/hkp/key_info.rb new file mode 100644 index 0000000..adb75d8 --- /dev/null +++ b/lib/nickserver/hkp/key_info.rb @@ -0,0 +1,69 @@ +require 'cgi' + +# +# Class to represent the key information result from a query to a key server +# (but not the key itself). +# +# The initialize method parses the hkp 'machine readable' output. +# +# 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 + class KeyInfo + attr_accessor :uids, :keyid, :algo, :keylen, :creationdate, :expirationdate, :flags + + def initialize(hkp_record) + uid_lines = hkp_record.split("\n") + pub_line = uid_lines.shift + @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] + # for now, ignore the expirationdate and flags of uids. sks does return them anyway + @uids << CGI.unescape(uid.sub(/.*<(.+)>.*/, '\1')) + end + end + + def keylen + @keylen ||= @keylen_s.to_i + end + + def creationdate + @creationdate ||= begin + if @creationdate_s + Time.at(@creationdate_s.to_i) + end + end + end + + def expirationdate + @expirationdate ||= begin + if @expirationdate_s + Time.at(@expirationdate_s.to_i) + end + end + end + + def rsa? + @algo == "1" + end + + def dsa? + @algo == "17" + end + + def revoked? + @flags =~ /r/ + end + + def disabled? + @flags =~ /d/ + end + + def expired? + @flags =~ /e/ + end + end + +end; end diff --git a/lib/nickserver/server.rb b/lib/nickserver/server.rb new file mode 100644 index 0000000..4201cef --- /dev/null +++ b/lib/nickserver/server.rb @@ -0,0 +1,70 @@ +require 'eventmachine' +require 'evma_httpserver' + + +# +# This is the main HTTP server that clients connect to in order to fetch keys +# +module Nickserver + class Server < EM::Connection + include EM::HttpServer + + def post_init + super + no_environment_strings + end + + def process_http_request + # the http request details are available via the following instance variables: + # @http_protocol + # @http_request_method + # @http_cookie + # @http_if_none_match + # @http_content_type + # @http_path_info + # @http_request_uri + # @http_query_string + # @http_post_content + # @http_headers + if @http_request_method == "GET" + if @http_path_info =~ /^\/key\// + send_key + else + send_error("malformed path: #{@http_path_info}") + end + else + send_error("only GET is supported") + end + end + + private + + def send_error(msg = "not supported") + send_response(:status => 500, :content => msg) + end + + def send_key + uid = CGI.unescape @http_path_info.sub(/^\/key\/(.*)/, '\1') + get_key_from_uid(uid) do |key| + send_response(:content => key) + end + end + + def send_response(opts = {}) + options = {:status => 200, :content_type => 'text/plain', :content => ''}.merge(opts) + response = EM::DelegatedHttpResponse.new(self) + response.status = options[:status] + response.content_type options[:content_type] + response.content = options[:content] + response.send_response + end + + def get_key_from_uid(uid) + Nickserver::HKP::FetchKey.new.get(uid).callback {|key| + yield key + }.errback {|status| + send_response(:status => status, :content => 'could not fetch key') + } + end + end +end \ No newline at end of file diff --git a/lib/nickserver/version.rb b/lib/nickserver/version.rb new file mode 100644 index 0000000..799ca6c --- /dev/null +++ b/lib/nickserver/version.rb @@ -0,0 +1,3 @@ +module Nickserver + VERSION = "0.0.1" +end -- cgit v1.2.3