summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorelijah <elijah@riseup.net>2012-12-24 02:02:24 -0800
committerelijah <elijah@riseup.net>2012-12-24 02:02:24 -0800
commit457c2bf135be68b3c49ff20d3f23a6f8507aeda5 (patch)
tree73afc8568c6a83706111b19d35ba04d0cd94b2e0 /lib
initial commit
Diffstat (limited to 'lib')
-rw-r--r--lib/nickserver.rb10
-rw-r--r--lib/nickserver/config.rb14
-rw-r--r--lib/nickserver/hkp/fetch_key.rb51
-rw-r--r--lib/nickserver/hkp/fetch_key_info.rb66
-rw-r--r--lib/nickserver/hkp/key_info.rb69
-rw-r--r--lib/nickserver/server.rb70
-rw-r--r--lib/nickserver/version.rb3
7 files changed, 283 insertions, 0 deletions
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