checks.rb 9.62 KB
Newer Older
1
def shipped_openpgp_keys
2
  shipped_gpg_keys = $vm.execute_successfully('gpg --batch --with-colons --fingerprint --list-key', :user => LIVE_USER).stdout
3
4
5
6
7
8
9
10
  openpgp_fingerprints = shipped_gpg_keys.scan(/^fpr:::::::::([A-Z0-9]+):$/).flatten
  return openpgp_fingerprints
end

Then /^the OpenPGP keys shipped with Tails will be valid for the next (\d+) months$/ do |months|
  invalid = Array.new
  shipped_openpgp_keys.each do |key|
    begin
kytv's avatar
kytv committed
11
      step "the shipped OpenPGP key #{key} will be valid for the next #{months} months"
12
13
14
15
16
17
18
19
    rescue Test::Unit::AssertionFailedError
      invalid << key
      next
    end
  end
  assert(invalid.empty?, "The following key(s) will not be valid in #{months} months: #{invalid.join(', ')}")
end

kytv's avatar
kytv committed
20
Then /^the shipped (?:Debian repository key|OpenPGP key ([A-Z0-9]+)) will be valid for the next (\d+) months$/ do |fingerprint, max_months|
21
  if fingerprint
22
    cmd = 'gpg'
23
    user = LIVE_USER
kytv's avatar
kytv committed
24
  else
kytv's avatar
kytv committed
25
    fingerprint = TAILS_DEBIAN_REPO_KEY
26
27
28
    cmd = 'apt-key adv'
    user = 'root'
  end
29
  shipped_sig_key_info = $vm.execute_successfully("#{cmd} --batch --list-key #{fingerprint}", :user => user).stdout
30
31
32
33
  m = /\[expire[ds]: ([0-9-]*)\]/.match(shipped_sig_key_info)
  if m
    expiration_date = Date.parse(m[1])
    assert((expiration_date << max_months.to_i) > DateTime.now,
kytv's avatar
kytv committed
34
           "The shipped key #{fingerprint} will not be valid #{max_months} months from now.")
35
  end
36
37
end

38
39
40
Then /^I double-click the Report an Error launcher on the desktop$/ do
  @screen.wait_and_double_click('DesktopReportAnError.png', 30)
end
41
42

Then /^the live user has been setup by live\-boot$/ do
43
  assert($vm.execute("test -e /var/lib/live/config/user-setup").success?,
44
         "live-boot failed its user-setup")
45
  actual_username = $vm.execute(". /etc/live/config/username.conf; " +
46
                                "echo $LIVE_USERNAME").stdout.chomp
47
  assert_equal(LIVE_USER, actual_username)
48
49
50
end

Then /^the live user is a member of only its own group and "(.*?)"$/ do |groups|
51
  expected_groups = groups.split(" ") << LIVE_USER
52
  actual_groups = $vm.execute("groups #{LIVE_USER}").stdout.chomp.sub(/^#{LIVE_USER} : /, "").split(" ")
53
54
  unexpected = actual_groups - expected_groups
  missing = expected_groups - actual_groups
55
  assert_equal(0, unexpected.size,
56
         "live user in unexpected groups #{unexpected}")
57
  assert_equal(0, missing.size,
58
59
60
61
         "live user not in expected groups #{missing}")
end

Then /^the live user owns its home dir and it has normal permissions$/ do
62
  home = "/home/#{LIVE_USER}"
63
  assert($vm.execute("test -d #{home}").success?,
64
         "The live user's home doesn't exist or is not a directory")
65
66
  owner = $vm.execute("stat -c %U:%G #{home}").stdout.chomp
  perms = $vm.execute("stat -c %a #{home}").stdout.chomp
67
  assert_equal("#{LIVE_USER}:#{LIVE_USER}", owner)
68
  assert_equal("700", perms)
69
70
71
end

Then /^no unexpected services are listening for network connections$/ do
72
  netstat_cmd = $vm.execute("netstat -ltupn")
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
  assert netstat_cmd.success?
  for line in netstat_cmd.stdout.chomp.split("\n") do
    splitted = line.split(/[[:blank:]]+/)
    proto = splitted[0]
    if proto == "tcp"
      proc_index = 6
    elsif proto == "udp"
      proc_index = 5
    else
      next
    end
    laddr, lport = splitted[3].split(":")
    proc = splitted[proc_index].split("/")[1]
    # Services listening on loopback is not a threat
    if /127(\.[[:digit:]]{1,3}){3}/.match(laddr).nil?
88
89
      if SERVICES_EXPECTED_ON_ALL_IFACES.include? [proc, laddr, lport] or
         SERVICES_EXPECTED_ON_ALL_IFACES.include? [proc, laddr, "*"]
90
91
92
93
94
95
96
97
98
        puts "Service '#{proc}' is listening on #{laddr}:#{lport} " +
             "but has an exception"
      else
        raise "Unexpected service '#{proc}' listening on #{laddr}:#{lport}"
      end
    end
  end
end

99
When /^Tails has booted a 64-bit kernel$/ do
100
  assert($vm.execute("uname -r | grep -qs 'amd64$'").success?,
101
         "Tails has not booted a 64-bit kernel.")
102
103
end

104
105
Then /^there is no screenshot in the live user's Pictures directory$/ do
  pictures_directory = "/home/#{LIVE_USER}/Pictures"
106
  assert($vm.execute(
107
108
109
          "find '#{pictures_directory}' -name 'Screenshot*.png' -maxdepth 1"
        ).stdout.empty?,
         "Existing screenshots were found in the live user's Pictures directory.")
110
111
end

112
113
Then /^a screenshot is saved to the live user's Pictures directory$/ do
  pictures_directory = "/home/#{LIVE_USER}/Pictures"
114
115
  try_for(10, :msg=> "No screenshot was created in #{pictures_directory}") do
    !$vm.execute(
116
117
      "find '#{pictures_directory}' -name 'Screenshot*.png' -maxdepth 1"
    ).stdout.empty?
118
  end
119
120
end

121
Then /^the VirtualBox guest modules are available$/ do
122
  assert($vm.execute("modinfo vboxguest").success?,
123
124
125
         "The vboxguest module is not available.")
end

126
Given /^I setup a filesystem share containing a sample PDF$/ do
127
128
129
130
131
132
133
  shared_pdf_dir_on_host = "#{$config["TMPDIR"]}/shared_pdf_dir"
  @shared_pdf_dir_on_guest = "/tmp/shared_pdf_dir"
  FileUtils.mkdir_p(shared_pdf_dir_on_host)
  Dir.glob("#{MISC_FILES_DIR}/*.pdf") do |pdf_file|
    FileUtils.cp(pdf_file, shared_pdf_dir_on_host)
  end
  add_after_scenario_hook { FileUtils.rm_r(shared_pdf_dir_on_host) }
134
  $vm.add_share(shared_pdf_dir_on_host, @shared_pdf_dir_on_guest)
135
136
end

kytv's avatar
kytv committed
137
138
139
140
Then /^the support documentation page opens in Tor Browser$/ do
  @screen.wait("SupportDocumentation#{@language}.png", 120)
end

141
Then /^MAT can clean some sample PDF file$/ do
142
  for pdf_on_host in Dir.glob("#{MISC_FILES_DIR}/*.pdf") do
143
    pdf_name = File.basename(pdf_on_host)
144
    pdf_on_guest = "/home/#{LIVE_USER}/#{pdf_name}"
145
    step "I copy \"#{@shared_pdf_dir_on_guest}/#{pdf_name}\" to \"#{pdf_on_guest}\" as user \"#{LIVE_USER}\""
146
    check_before = $vm.execute_successfully("mat --check '#{pdf_on_guest}'",
anonym's avatar
anonym committed
147
                                            :user => LIVE_USER).stdout
148
149
    assert(check_before.include?("#{pdf_on_guest} is not clean"),
           "MAT failed to see that '#{pdf_on_host}' is dirty")
150
    $vm.execute_successfully("mat '#{pdf_on_guest}'", :user => LIVE_USER)
151
    check_after = $vm.execute_successfully("mat --check '#{pdf_on_guest}'",
anonym's avatar
anonym committed
152
                                           :user => LIVE_USER).stdout
153
154
    assert(check_after.include?("#{pdf_on_guest} is clean"),
           "MAT failed to clean '#{pdf_on_host}'")
155
    $vm.execute_successfully("rm '#{pdf_on_guest}'")
156
157
  end
end
158
159

Then /^AppArmor is enabled$/ do
160
  assert($vm.execute("aa-status").success?, "AppArmor is not enabled")
161
162
163
end

Then /^some AppArmor profiles are enforced$/ do
164
  assert($vm.execute("aa-status --enforced").stdout.chomp.to_i > 0,
165
166
         "No AppArmor profile is enforced")
end
167

168
def get_seccomp_status(process)
169
170
171
  assert($vm.has_process?(process), "Process #{process} not running.")
  pid = $vm.pidof(process)[0]
  status = $vm.file_content("/proc/#{pid}/status")
172
173
174
  return status.match(/^Seccomp:\s+([0-9])/)[1].chomp.to_i
end

175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
def get_apparmor_status(pid)
  apparmor_status = $vm.file_content("/proc/#{pid}/attr/current").chomp
  if apparmor_status.include?(')')
    # matches something like     /usr/sbin/cupsd (enforce)
    # and only returns what's in the parentheses
    return apparmor_status.match(/[^\s]+\s+\((.+)\)$/)[1].chomp
  else
    return apparmor_status
  end
end

Then /^the running process "(.+)" is confined with AppArmor in (complain|enforce) mode$/ do |process, mode|
  if process == 'i2p'
    $vm.execute_successfully('service i2p status')
    pid = $vm.file_content('/run/i2p/i2p.pid').chomp
  else
    assert($vm.has_process?(process), "Process #{process} not running.")
    pid = $vm.pidof(process)[0]
  end
194
  assert_equal(mode, get_apparmor_status(pid))
195
196
end

197
198
199
200
201
202
203
204
205
Then /^the running process "(.+)" is confined with Seccomp in (filter|strict) mode$/ do |process,mode|
  status = get_seccomp_status(process)
  if mode == 'strict'
    assert_equal(1, status, "#{process} not confined with Seccomp in strict mode")
  elsif mode == 'filter'
    assert_equal(2, status, "#{process} not confined with Seccomp in filter mode")
  else
    raise "Unsupported mode #{mode} passed"
  end
206
end
207
208
209
210
211
212
213
214
215
216

Then /^tails-debugging-info is not susceptible to symlink attacks$/ do
  secret_file = '/secret'
  secret_contents = 'T0P S3Cr1t -- 3yEs oN1y'
  $vm.file_append(secret_file, secret_contents)
  $vm.execute_successfully("chmod u=rw,go= #{secret_file}")
  $vm.execute_successfully("chown root:root #{secret_file}")
  script_path = '/usr/local/sbin/tails-debugging-info'
  script_lines = $vm.file_content(script_path).split("\n")
  script_lines.grep(/^debug_file\s+/).each do |line|
217
218
219
220
221
    _, user, debug_file = line.split
    # root can always mount symlink attacks
    next if user == 'root'
    # Remove quoting around the file
    debug_file.gsub!(/["']/, '')
222
223
224
225
226
227
    # Skip files that do not exist, or cannot be removed (e.g. the
    # ones in /proc).
    next if not($vm.execute("rm #{debug_file}").success?)
    # Check what would happen *if* the amnesia user managed to replace
    # the debugging file with a symlink to the secret.
    $vm.execute_successfully("ln -s #{secret_file} #{debug_file}")
anonym's avatar
anonym committed
228
    $vm.execute_successfully("chown --no-dereference #{LIVE_USER}:#{LIVE_USER} #{debug_file}")
229
230
231
    if $vm.execute("sudo /usr/local/sbin/tails-debugging-info | " +
                   "grep '#{secret_contents}'",
                   :user => LIVE_USER).success?
232
233
      raise "The secret was leaked by tails-debugging-info via '#{debug_file}'"
    end
234
235
236
    # Remove the secret so it cannot possibly interfere with the
    # following iterations (even though it should not).
    $vm.execute_successfully("echo > #{debug_file}")
237
238
  end
end
239
240
241
242
243
244
245
246
247

When /^I disable all networking in the Tails Greeter$/ do
  begin
    @screen.click('TailsGreeterDisableAllNetworking.png')
  rescue FindFailed
    @screen.type(Sikuli::Key.PAGE_DOWN)
    @screen.click('TailsGreeterDisableAllNetworking.png')
  end
end