File: hkp.rb

package info (click to toggle)
ruby-mail-gpg 0.4.4-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bullseye, forky, sid, trixie
  • size: 328 kB
  • sloc: ruby: 2,289; makefile: 6
file content (156 lines) | stat: -rw-r--r-- 4,124 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
require 'gpgme'
require 'openssl'
require 'net/http'

# simple HKP client for public key search and retrieval
class Hkp

  class TooManyRedirects < StandardError; end

  class InvalidResponse < StandardError; end


  class Client

    MAX_REDIRECTS = 3

    def initialize(server, ssl_verify_mode: OpenSSL::SSL::VERIFY_PEER)
      uri = URI server
      @host = uri.host
      @port = uri.port
      @use_ssl = false
      @ssl_verify_mode = ssl_verify_mode

      # set port and ssl flag according to URI scheme
      case uri.scheme.downcase
      when 'hkp'
        # use the HKP default port unless another port has been given
        @port ||= 11371
      when /\A(hkp|http)s\z/
        # hkps goes through 443 by default
        @port ||= 443
        @use_ssl = true
      end
      @port ||= 80
    end


    def get(path, redirect_depth = 0)
      Net::HTTP.start @host, @port, use_ssl: @use_ssl,
                                    verify_mode: @ssl_verify_mode do |http|

        request = Net::HTTP::Get.new path
        response = http.request request

        case response.code.to_i
        when 200
          return response.body
        when 301, 302
          if redirect_depth >= MAX_REDIRECTS
            raise TooManyRedirects
          else
            http_get response['location'], redirect_depth + 1
          end
        else
          raise InvalidResponse, response.code
        end

      end
    end

  end


  def initialize(options = {})
    if String === options
      options = { keyserver: options }
    end
    @keyserver = options.delete(:keyserver) || lookup_keyserver || 'http://pool.sks-keyservers.net:11371'
    @options = { raise_errors: true }.merge options
  end

  def raise_errors?
    !!@options[:raise_errors]
  end

  #
  # hkp.search 'user@host.com'
  # will return an array of arrays, one for each matching key found, containing
  # the key id as the first elment and any further info returned by the key
  # server in the following elements.
  # see http://tools.ietf.org/html/draft-shaw-openpgp-hkp-00#section-5.2 for
  # what that *might* be. unfortunately key servers seem to differ in how much
  # and what info they return besides the key id
  def search(name)
    [].tap do |results|
      result = hkp_client.get "/pks/lookup?options=mr&search=#{URI.escape name}"

      result.each_line do |l|
        components = l.strip.split(':')
        if components.shift == 'pub'
          results << components
        end
      end if result
    end

  rescue
    raise $! if raise_errors?
    nil
  end


  # returns the key data as returned from the server as a string
  def fetch(id)
    result = hkp_client.get "/pks/lookup?options=mr&op=get&search=0x#{URI.escape id}"
    return clean_key(result) if result

  rescue Exception
    raise $! if raise_errors?
    nil
  end


  # fetches key data by id and imports the found key(s) into GPG, returning the full hex fingerprints of the
  # imported key(s) as an array. Given there are no collisions with the id given / the server has returned
  # exactly one key this will be a one element array.
  def fetch_and_import(id)
    if key = fetch(id)
      GPGME::Key.import(key).imports.map(&:fpr)
    end
  rescue Exception
    raise $! if raise_errors?
  end

  private

  def hkp_client
    @hkp_client ||= Client.new @keyserver, ssl_verify_mode: @options[:ssl_verify_mode]
  end

  def clean_key(key)
    if key =~ /(-----BEGIN PGP PUBLIC KEY BLOCK-----.*-----END PGP PUBLIC KEY BLOCK-----)/m
      return $1
    end
  end

  def exec_cmd(cmd)
    res = `#{cmd}`
    return nil if $?.exitstatus != 0
    res
  end

  def lookup_keyserver
    url = nil
    if res = exec_cmd("gpgconf --list-options gpgs 2>&1 | grep keyserver 2>&1")
      url = URI.decode(res.split(":").last.split("\"").last.strip)
    elsif res = exec_cmd("gpg --gpgconf-list 2>&1 | grep gpgconf-gpg.conf 2>&1")
      conf_file = res.split(":").last.split("\"").last.strip
      if res = exec_cmd("cat #{conf_file} 2>&1 | grep ^keyserver 2>&1")
        url = res.split(" ").last.strip
      end
    end
    url =~ /^(http|hkp)/ ? url : nil
  end

end