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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
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
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
|
require 'resolv'
class OpenPGPKeyserverCommunicationError < StandardError
end
def count_gpg_signatures(key)
output = $vm.execute_successfully("gpg --batch --list-sigs #{key}",
:user => LIVE_USER).stdout
output.scan(/^sig/).count
end
def check_for_seahorse_error
if @screen.exists('GnomeCloseButton.png')
raise OpenPGPKeyserverCommunicationError.new(
"Found GnomeCloseButton.png' on the screen"
)
end
end
def start_or_restart_seahorse
assert_not_nil(@withgpgapplet)
if @withgpgapplet
seahorse_menu_click_helper('GpgAppletIconNormal.png', 'GpgAppletManageKeys.png')
else
step 'I start "Passwords and Keys" via GNOME Activities Overview'
end
step 'Seahorse has opened'
end
Then /^the key "([^"]+)" has (only|more than) (\d+) signatures$/ do |key, qualifier, num|
count = count_gpg_signatures(key)
case qualifier
when 'only'
assert_equal(count, num.to_i, "Expected #{num} signatures but instead found #{count}")
when 'more than'
assert(count > num.to_i, "Expected more than #{num} signatures but found #{count}")
else
raise "Unknown operator #{qualifier} passed"
end
end
When /^the "([^"]+)" OpenPGP key is not in the live user's public keyring$/ do |keyid|
assert(!$vm.execute("gpg --batch --list-keys '#{keyid}'",
:user => LIVE_USER).success?,
"The '#{keyid}' key is in the live user's public keyring.")
end
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
When /^I fetch the "([^"]+)" OpenPGP key using the GnuPG CLI( without any signatures)?$/ do |keyid, without|
# Make keyid an instance variable so we can reference it in the Seahorse
# keysyncing step.
@fetched_openpgp_keyid = keyid
if without
importopts = '--keyserver-options import-clean'
else
importopts = ''
end
retry_tor(Proc.new { setup_onion_keyserver }) do
@gnupg_recv_key_res = $vm.execute_successfully(
"timeout 120 gpg --batch #{importopts} --recv-key '#{@fetched_openpgp_keyid}'",
:user => LIVE_USER)
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}"
end
end
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
When /^the Seahorse operation is successful$/ do
!@screen.exists('GnomeCloseButton.png')
$vm.has_process?('seahorse')
end
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") {
$vm.execute("gpg --batch --list-keys '#{keyid}'",
:user => LIVE_USER).success?
}
end
When /^I start Seahorse( via the OpenPGP Applet)?$/ do |withgpgapplet|
@withgpgapplet = !!withgpgapplet
start_or_restart_seahorse
end
Then /^Seahorse has opened$/ do
@screen.wait('SeahorseWindow.png', 20)
end
Then /^I enable key synchronization in Seahorse$/ do
step 'process "seahorse" is running'
@screen.wait_and_click("SeahorseWindow.png", 10)
seahorse_menu_click_helper('GnomeEditMenu.png', 'SeahorseEditPreferences.png', 'seahorse')
@screen.wait('SeahorsePreferences.png', 20)
@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
Then /^I synchronize keys in Seahorse$/ do
recovery_proc = Proc.new do
setup_onion_keyserver
# The version of Seahorse in Jessie will abort with a
# segmentation fault whenever there's any sort of network error while
# syncing keys. This will usually happens after clicking away the error
# message. This does not appear to be a problem in Stretch.
#
# 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.')
start_or_restart_seahorse
end
end
def change_of_status?
# 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 count_gpg_signatures(@fetched_openpgp_keyid) > 2 || \
@screen.exists('GnomeCloseButton.png') || \
!$vm.has_process?('seahorse')
true
end
end
retry_tor(recovery_proc) do
@screen.wait_and_click("SeahorseWindow.png", 10)
seahorse_menu_click_helper('SeahorseRemoteMenu.png',
'SeahorseRemoteMenuSync.png',
'seahorse')
@screen.wait('SeahorseSyncKeys.png', 20)
@screen.type("s", Sikuli::KeyModifier.ALT) # Button: Sync
# There's no visual feedback of Seahorse in Tails/Jessie, except on error.
try_for(120) {
change_of_status?
}
check_for_seahorse_error
raise OpenPGPKeyserverCommunicationError.new(
'Seahorse crashed with a segfault.') unless $vm.has_process?('seahorse')
end
end
When /^I fetch the "([^"]+)" OpenPGP key using Seahorse( via the OpenPGP Applet)?$/ do |keyid, withgpgapplet|
step "I start Seahorse#{withgpgapplet}"
def change_of_status?(keyid)
# 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
end
recovery_proc = Proc.new do
setup_onion_keyserver
@screen.click('GnomeCloseButton.png') if @screen.exists('GnomeCloseButton.png')
@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')
@screen.wait('SeahorseFindKeysWindow.png', 10)
# Seahorse doesn't seem to support searching for fingerprints
@screen.type(keyid + Sikuli::Key.ENTER)
begin
@screen.waitAny(['SeahorseFoundKeyResult.png',
'GnomeCloseButton.png'], 120)
rescue FindAnyFailed
# 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")
end
check_for_seahorse_error
@screen.click("SeahorseKeyResultWindow.png")
@screen.click("SeahorseFoundKeyResult.png")
@screen.click("SeahorseImport.png")
try_for(120) do
change_of_status?(keyid)
end
check_for_seahorse_error
end
end
Given /^(GnuPG|Seahorse) is configured to use Chutney's onion keyserver$/ do |app|
setup_onion_keyserver unless @onion_keyserver_job
_, _, onion_address, onion_port = chutney_onionservice_info
case app
when 'GnuPG'
# Validate the shipped configuration ...
server = /keyserver\s+(\S+)$/.match($vm.file_content("/home/#{LIVE_USER}/.gnupg/dirmngr.conf"))[1]
assert_equal(
"hkp://#{CONFIGURED_KEYSERVER_HOSTNAME}", server,
"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
end
Then /^GnuPG's dirmngr uses the configured keyserver$/ do
_, _, onion_keyserver_address, _ = chutney_onionservice_info
dirmngr_request = $vm.execute_successfully(
'gpg-connect-agent --dirmngr "keyserver --hosttable" /bye', user: LIVE_USER
)
server = dirmngr_request.stdout.chomp.lines[1].split[4]
server = /keyserver\s+(\S+)$/.match(
$vm.file_content("/home/#{LIVE_USER}/.gnupg/dirmngr.conf")
)[1]
assert_equal(
"hkp://#{onion_keyserver_address}:5858", server,
"GnuPG's dirmngr does not use the correct keyserver"
)
end
|