torified_gnupg.rb 8.71 KB
Newer Older
1
2
require 'resolv'

3
4
5
class OpenPGPKeyserverCommunicationError < StandardError
end

kytv's avatar
kytv committed
6
def count_gpg_signatures(key)
7
  output = $vm.execute_successfully("gpg --batch --list-sigs #{key}",
anonym's avatar
anonym committed
8
                                    :user => LIVE_USER).stdout
kytv's avatar
kytv committed
9
  output.scan(/^sig/).count
10
11
end

12
13
14
15
16
17
18
19
def check_for_seahorse_error
  if @screen.exists('GnomeCloseButton.png')
    raise OpenPGPKeyserverCommunicationError.new(
      "Found GnomeCloseButton.png' on the screen"
    )
  end
end

20
21
22
def start_or_restart_seahorse
  assert_not_nil(@withgpgapplet)
  if @withgpgapplet
kytv's avatar
kytv committed
23
24
    seahorse_menu_click_helper('GpgAppletIconNormal.png', 'GpgAppletManageKeys.png')
  else
25
    step 'I start "Passwords and Keys" via the GNOME "Utilities" applications menu'
26
  end
kytv's avatar
kytv committed
27
  step 'Seahorse has opened'
28
29
end

30
Then /^the key "([^"]+)" has (only|more than) (\d+) signatures$/ do |key, qualifier, num|
kytv's avatar
kytv committed
31
  count = count_gpg_signatures(key)
32
33
  case qualifier
  when 'only'
kytv's avatar
kytv committed
34
    assert_equal(count, num.to_i, "Expected #{num} signatures but instead found #{count}")
35
  when 'more than'
kytv's avatar
kytv committed
36
    assert(count > num.to_i, "Expected more than #{num} signatures but found #{count}")
37
38
39
40
41
  else
    raise "Unknown operator #{qualifier} passed"
  end
end

Tails developers's avatar
Tails developers committed
42
When /^the "([^"]+)" OpenPGP key is not in the live user's public keyring$/ do |keyid|
43
  assert(!$vm.execute("gpg --batch --list-keys '#{keyid}'",
anonym's avatar
anonym committed
44
                      :user => LIVE_USER).success?,
45
         "The '#{keyid}' key is in the live user's public keyring.")
Tails developers's avatar
Tails developers committed
46
47
end

48
49
50
51
52
53
54
55
56
57
58
59
def setup_onion_keyserver
  resolver = Resolv::DNS.new
  keyservers = resolver.getaddresses('pool.sks-keyservers.net').select do |addr|
    addr.class == Resolv::IPv4
  end
  onion_keyserver_address = keyservers.sample
  hkp_port = 11371
  @onion_keyserver_job = chutney_onionservice_redir(
    onion_keyserver_address, hkp_port
  )
end

60
When /^I fetch the "([^"]+)" OpenPGP key using the GnuPG CLI( without any signatures)?$/ do |keyid, without|
61
62
  # Make keyid an instance variable so we can reference it in the Seahorse
  # keysyncing step.
kytv's avatar
kytv committed
63
  @fetched_openpgp_keyid = keyid
64
65
66
67
68
  if without
    importopts = '--keyserver-options import-clean'
  else
    importopts = ''
  end
69
  retry_tor(Proc.new { setup_onion_keyserver }) do
70
    @gnupg_recv_key_res = $vm.execute_successfully(
kytv's avatar
kytv committed
71
      "timeout 120 gpg --batch #{importopts} --recv-key '#{@fetched_openpgp_keyid}'",
anonym's avatar
anonym committed
72
      :user => LIVE_USER)
anonym's avatar
anonym committed
73
74
75
76
    if @gnupg_recv_key_res.failure?
      raise "Fetching keys with the GnuPG CLI failed with:\n" +
            "#{@gnupg_recv_key_res.stdout}\n" +
            "#{@gnupg_recv_key_res.stderr}"
77
78
    end
  end
79
80
81
82
83
84
85
end

When /^the GnuPG fetch is successful$/ do
  assert(@gnupg_recv_key_res.success?,
         "gpg keyserver fetch failed:\n#{@gnupg_recv_key_res.stderr}")
end

86
87
88
89
90
When /^the Seahorse operation is successful$/ do
  !@screen.exists('GnomeCloseButton.png')
  $vm.has_process?('seahorse')
end

kytv's avatar
kytv committed
91
92
93
When /^the "([^"]+)" key is in the live user's public keyring(?: after at most (\d) seconds)?$/ do |keyid, delay|
  delay = 10 unless delay
  try_for(delay.to_i, :msg => "The '#{keyid}' key is not in the live user's public keyring") {
94
    $vm.execute("gpg --batch --list-keys '#{keyid}'",
anonym's avatar
anonym committed
95
                :user => LIVE_USER).success?
96
97
98
  }
end

99
When /^I start Seahorse( via the OpenPGP Applet)?$/ do |withgpgapplet|
100
101
  @withgpgapplet = !!withgpgapplet
  start_or_restart_seahorse
102
103
end

104
Then /^Seahorse has opened$/ do
kytv's avatar
kytv committed
105
  @screen.wait('SeahorseWindow.png', 20)
106
107
end

108
Then /^I enable key synchronization in Seahorse$/ do
kytv's avatar
kytv committed
109
  step 'process "seahorse" is running'
110
  @screen.wait_and_click("SeahorseWindow.png", 10)
111
  seahorse_menu_click_helper('GnomeEditMenu.png', 'SeahorseEditPreferences.png', 'seahorse')
kytv's avatar
kytv committed
112
  @screen.wait('SeahorsePreferences.png', 20)
113
114
115
116
117
  @screen.type("p", Sikuli::KeyModifier.ALT) # Option: "Publish keys to...".
  @screen.type(Sikuli::Key.DOWN) # select HKP server
  @screen.type("c", Sikuli::KeyModifier.ALT) # Button: "Close"
end

118
Then /^I synchronize keys in Seahorse$/ do
anonym's avatar
anonym committed
119
  recovery_proc = Proc.new do
120
    setup_onion_keyserver
121
    # The version of Seahorse in Jessie will abort with a
122
123
    # segmentation fault whenever there's any sort of network error while
    # syncing keys. This will usually happens after clicking away the error
kytv's avatar
kytv committed
124
    # message. This does not appear to be a problem in Stretch.
125
126
127
128
129
130
131
    #
    # We'll kill the Seahorse process to avoid waiting for the inevitable
    # segfault. We'll also make sure the process is still running (=  hasn't
    # yet segfaulted) before terminating it.
    if @screen.exists('GnomeCloseButton.png') || !$vm.has_process?('seahorse')
      step 'I kill the process "seahorse"' if $vm.has_process?('seahorse')
      debug_log('Restarting Seahorse.')
132
      start_or_restart_seahorse
133
134
135
    end
  end

136
  def change_of_status?
137
138
139
    # Due to a lack of visual feedback in Seahorse we'll break out of the
    # try_for loop below by returning "true" when there's something we can act
    # upon.
kytv's avatar
kytv committed
140
    if count_gpg_signatures(@fetched_openpgp_keyid) > 2 || \
141
142
143
      @screen.exists('GnomeCloseButton.png')  || \
      !$vm.has_process?('seahorse')
        true
144
    end
anonym's avatar
anonym committed
145
  end
146

anonym's avatar
anonym committed
147
148
149
150
151
  retry_tor(recovery_proc) do
    @screen.wait_and_click("SeahorseWindow.png", 10)
    seahorse_menu_click_helper('SeahorseRemoteMenu.png',
                               'SeahorseRemoteMenuSync.png',
                               'seahorse')
kytv's avatar
kytv committed
152
    @screen.wait('SeahorseSyncKeys.png', 20)
anonym's avatar
anonym committed
153
    @screen.type("s", Sikuli::KeyModifier.ALT) # Button: Sync
154
    # There's no visual feedback of Seahorse in Tails/Jessie, except on error.
155
    try_for(120) {
156
      change_of_status?
157
    }
158
    check_for_seahorse_error
159
160
161
    raise OpenPGPKeyserverCommunicationError.new(
      'Seahorse crashed with a segfault.') unless $vm.has_process?('seahorse')
   end
162
163
end

164
When /^I fetch the "([^"]+)" OpenPGP key using Seahorse( via the OpenPGP Applet)?$/ do |keyid, withgpgapplet|
165
  step "I start Seahorse#{withgpgapplet}"
anonym's avatar
anonym committed
166

167
  def change_of_status?(keyid)
kytv's avatar
kytv committed
168
169
170
171
172
173
174
175
    # Due to a lack of visual feedback in Seahorse we'll break out of the
    # try_for loop below by returning "true" when there's something we can act
    # upon.
    if $vm.execute_successfully(
      "gpg --batch --list-keys '#{keyid}'", :user => LIVE_USER) ||
      @screen.exists('GnomeCloseButton.png')
      true
    end
176
  end
anonym's avatar
anonym committed
177
178

  recovery_proc = Proc.new do
179
    setup_onion_keyserver
kytv's avatar
kytv committed
180
    @screen.click('GnomeCloseButton.png') if @screen.exists('GnomeCloseButton.png')
anonym's avatar
anonym committed
181
182
183
184
185
186
187
    @screen.type("w", Sikuli::KeyModifier.CTRL)
  end
  retry_tor(recovery_proc) do
    @screen.wait_and_click("SeahorseWindow.png", 10)
    seahorse_menu_click_helper('SeahorseRemoteMenu.png',
                               'SeahorseRemoteMenuFind.png',
                               'seahorse')
kytv's avatar
kytv committed
188
    @screen.wait('SeahorseFindKeysWindow.png', 10)
anonym's avatar
anonym committed
189
190
    # Seahorse doesn't seem to support searching for fingerprints
    @screen.type(keyid + Sikuli::Key.ENTER)
191
    begin
192
      @screen.waitAny(['SeahorseFoundKeyResult.png',
193
                       'GnomeCloseButton.png'], 120)
194
    rescue FindAnyFailed
anonym's avatar
anonym committed
195
196
197
198
199
      # We may end up here if Seahorse appears to be "frozen".
      # Sometimes--but not always--if we click another window
      # the main Seahorse window will unfreeze, allowing us
      # to continue normally.
      @screen.click("SeahorseSearch.png")
200
    end
201
    check_for_seahorse_error
anonym's avatar
anonym committed
202
203
204
    @screen.click("SeahorseKeyResultWindow.png")
    @screen.click("SeahorseFoundKeyResult.png")
    @screen.click("SeahorseImport.png")
kytv's avatar
kytv committed
205
    try_for(120) do
206
      change_of_status?(keyid)
kytv's avatar
kytv committed
207
    end
208
    check_for_seahorse_error
kytv's avatar
kytv committed
209
  end
Tails developers's avatar
Tails developers committed
210
end
211

212
Given /^(GnuPG|Seahorse) is configured to use Chutney's onion keyserver$/ do |app|
213
  setup_onion_keyserver unless @onion_keyserver_job
214
215
216
217
  _, _, onion_address, onion_port = chutney_onionservice_info
  case app
  when 'GnuPG'
    # Validate the shipped configuration ...
218
    server = /keyserver\s+(\S+)$/.match($vm.file_content("/home/#{LIVE_USER}/.gnupg/dirmngr.conf"))[1]
219
    assert_equal(
220
      "hkp://#{CONFIGURED_KEYSERVER_HOSTNAME}", server,
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
      "GnuPG's dirmngr does not use the correct keyserver"
    )
    # ... before replacing it
    $vm.execute_successfully(
      "sed -i 's/#{CONFIGURED_KEYSERVER_HOSTNAME}/#{onion_address}:#{onion_port}/' " +
      "'/home/#{LIVE_USER}/.gnupg/dirmngr.conf'"
    )
  when 'Seahorse'
    # Validate the shipped configuration ...
    @gnome_keyservers = YAML.load(
      $vm.execute_successfully(
        'gsettings get org.gnome.crypto.pgp keyservers',
        user: LIVE_USER
      ).stdout
    )
    assert_equal(1, @gnome_keyservers.count,
                 'Seahorse should only have one keyserver configured.')
    assert_equal(
      'hkp://' + CONFIGURED_KEYSERVER_HOSTNAME, @gnome_keyservers[0],
      "GnuPG's dirmngr does not use the correct keyserver"
    )
    # ... before replacing it
    $vm.execute_successfully(
      "gsettings set org.gnome.crypto.pgp keyservers \"['hkp://#{onion_address}:#{onion_port}']\"",
      user: LIVE_USER
    )
  end
248
end