summaryrefslogtreecommitdiffstats
path: root/features
diff options
context:
space:
mode:
Diffstat (limited to 'features')
-rw-r--r--features/apt.feature5
-rw-r--r--features/checks.feature29
-rw-r--r--features/config/defaults.yml34
-rw-r--r--features/firewall_leaks.feature37
-rw-r--r--features/i2p.feature3
-rw-r--r--features/images/DesktopReportAnError.pngbin0 -> 3410 bytes
-rw-r--r--features/images/DesktopTailsDocumentationIcon.pngbin0 -> 4676 bytes
-rw-r--r--features/images/GeditCopy.pngbin0 -> 631 bytes
-rw-r--r--features/images/GeditEdit.pngbin0 -> 409 bytes
-rw-r--r--features/images/GeditNewDocument.pngbin0 -> 918 bytes
-rw-r--r--features/images/GeditPaste.pngbin0 -> 813 bytes
-rw-r--r--features/images/GeditSelectAll.pngbin0 -> 1574 bytes
-rw-r--r--features/images/GitSSHFingerprint.pngbin0 -> 3074 bytes
-rw-r--r--features/images/GnomeApplicationsGobby.pngbin0 -> 1861 bytes
-rw-r--r--features/images/GnomeCloseButton.pngbin0 -> 1069 bytes
-rw-r--r--features/images/GnomePlaces.pngbin0 -> 1055 bytes
-rw-r--r--features/images/GnomePlacesWithoutTorBrowserPersistent.pngbin0 -> 5558 bytes
-rw-r--r--features/images/GobbyConnectPrompt.pngbin0 -> 2020 bytes
-rw-r--r--features/images/GobbyConnectionComplete.pngbin0 -> 994 bytes
-rw-r--r--features/images/GobbyWelcomePrompt.pngbin0 -> 3411 bytes
-rw-r--r--features/images/GobbyWindow.pngbin0 -> 1271 bytes
-rw-r--r--features/images/GpgAppletDecryptVerify.pngbin0 -> 2434 bytes
-rw-r--r--features/images/GpgAppletEncryptPassphrase.pngbin0 -> 2650 bytes
-rw-r--r--features/images/GpgAppletSignEncrypt.pngbin0 -> 3397 bytes
-rw-r--r--features/images/GtkTorBrowserPersistentBookmark.pngbin0 -> 907 bytes
-rw-r--r--features/images/GtkTorBrowserPersistentBookmarkSelected.pngbin0 -> 2124 bytes
-rw-r--r--features/images/KeyImportedNotification.pngbin0 -> 3809 bytes
-rw-r--r--features/images/OpenWithImportKey.pngbin0 -> 2653 bytes
-rw-r--r--features/images/PidginTailsRoadmapUrl.pngbin0 -> 889 bytes
-rw-r--r--features/images/PrintToFile.png (renamed from features/images/EvincePrintToFile.png)bin1440 -> 1440 bytes
-rw-r--r--features/images/SSHAuthVerification.pngbin0 -> 2119 bytes
-rw-r--r--features/images/SupportDocumentation.pngbin0 -> 2314 bytes
-rw-r--r--features/images/SynapticApply.pngbin0 -> 1855 bytes
-rw-r--r--features/images/SynapticCowsayMarked.pngbin0 -> 2090 bytes
-rw-r--r--features/images/SynapticCowsaySearchResultSelected.pngbin0 -> 3687 bytes
-rw-r--r--features/images/SynapticPackageList.pngbin0 -> 4611 bytes
-rw-r--r--features/images/TailsGreeterTorConf.pngbin0 -> 7125 bytes
-rw-r--r--features/images/TailsOfflineDocHomepage.pngbin0 -> 4978 bytes
-rw-r--r--features/images/TorBrowserAmnesicFilesBookmark.pngbin0 -> 1920 bytes
-rw-r--r--features/images/TorBrowserBlockedVideo.pngbin0 -> 2814 bytes
-rw-r--r--features/images/TorBrowserCouldNotReadTheContentsOfWarning.pngbin0 -> 2724 bytes
-rw-r--r--features/images/TorBrowserHtml5PlayButton.pngbin0 -> 535 bytes
-rw-r--r--features/images/TorBrowserNoScriptTemporarilyAllowDialog.pngbin0 -> 1763 bytes
-rw-r--r--features/images/TorBrowserOkButton.pngbin0 -> 786 bytes
-rw-r--r--features/images/TorBrowserPersistentFilesBookmark.pngbin0 -> 2784 bytes
-rw-r--r--features/images/TorBrowserPrintDialog.pngbin0 -> 3517 bytes
-rw-r--r--features/images/TorBrowserPrintOutputFile.pngbin0 -> 1254 bytes
-rw-r--r--features/images/TorBrowserPrintOutputFileSelected.pngbin0 -> 1267 bytes
-rw-r--r--features/images/TorBrowserSampleRemoteHTML5VideoFrame.pngbin0 -> 110768 bytes
-rw-r--r--features/images/TorBrowserSampleRemoteWebMVideoFrame.pngbin0 -> 5185 bytes
-rw-r--r--features/images/TorBrowserSaveOutputFileSelected.pngbin0 -> 1563 bytes
-rw-r--r--features/images/TorBrowserSavedStartupPage.pngbin0 -> 2121 bytes
-rw-r--r--features/images/TorBrowserSynapticManual.pngbin0 -> 6018 bytes
-rw-r--r--features/images/TorBrowserTailsRoadmap.pngbin0 -> 2012 bytes
-rw-r--r--features/images/TorBrowserUnableToOpen.pngbin0 -> 1768 bytes
-rw-r--r--features/images/TorBrowserWarningDialogOkButton.pngbin0 -> 1304 bytes
-rw-r--r--features/images/TorLauncherBridgeList.pngbin0 -> 1512 bytes
-rw-r--r--features/images/TorLauncherBridgePrompt.pngbin0 -> 5221 bytes
-rw-r--r--features/images/TorLauncherConfigureButton.pngbin0 -> 1831 bytes
-rw-r--r--features/images/TorLauncherConnectingWindow.pngbin0 -> 2348 bytes
-rw-r--r--features/images/TorLauncherFinishButton.pngbin0 -> 671 bytes
-rw-r--r--features/images/TorLauncherNextButton.pngbin0 -> 816 bytes
-rw-r--r--features/images/TorLauncherWindow.pngbin0 -> 2127 bytes
-rw-r--r--features/images/TorLauncherYesRadioOption.pngbin0 -> 1016 bytes
l---------features/images/UnsafeBrowserAddressBar.png1
-rw-r--r--features/images/UnsafeBrowserDNSError.pngbin0 -> 3152 bytes
-rw-r--r--features/images/UnsafeBrowserExportBookmarksButton.pngbin0 -> 1734 bytes
-rw-r--r--features/images/UnsafeBrowserExportBookmarksMenuEntry.pngbin0 -> 1006 bytes
-rw-r--r--features/images/UnsafeBrowserExportBookmarksSavePrompt.pngbin0 -> 2291 bytes
-rw-r--r--features/images/UnsafeBrowserNewTabButton.pngbin0 -> 357 bytes
-rw-r--r--features/images/UnsafeBrowserNoAddons.pngbin0 -> 1995 bytes
-rw-r--r--features/images/UnsafeBrowserNoProxySelected.pngbin0 -> 1773 bytes
-rw-r--r--features/images/UnsafeBrowserStartVerification.pngbin3983 -> 2434 bytes
-rw-r--r--features/images/UnsafeBrowserTorCheckFail.pngbin5244 -> 6913 bytes
-rw-r--r--features/images/WindowsApplicationsTails.pngbin0 -> 1700 bytes
-rw-r--r--features/pidgin.feature12
-rw-r--r--features/po.feature8
-rwxr-xr-xfeatures/scripts/vm-execute52
-rw-r--r--features/step_definitions/apt.rb30
-rw-r--r--features/step_definitions/checks.rb116
-rw-r--r--features/step_definitions/common_steps.rb380
-rw-r--r--features/step_definitions/encryption.rb59
-rw-r--r--features/step_definitions/erase_memory.rb6
-rw-r--r--features/step_definitions/evince.rb2
-rw-r--r--features/step_definitions/firewall_leaks.rb20
-rw-r--r--features/step_definitions/git.rb24
-rw-r--r--features/step_definitions/i2p.rb4
-rw-r--r--features/step_definitions/pidgin.rb16
-rw-r--r--features/step_definitions/po.rb8
-rw-r--r--features/step_definitions/root_access_control.rb6
-rw-r--r--features/step_definitions/time_syncing.rb28
-rw-r--r--features/step_definitions/tor.rb375
-rw-r--r--features/step_definitions/torified_browsing.rb12
-rw-r--r--features/step_definitions/torified_gnupg.rb10
-rw-r--r--features/step_definitions/torified_misc.rb35
-rw-r--r--features/step_definitions/totem.rb6
-rw-r--r--features/step_definitions/unsafe_browser.rb137
-rw-r--r--features/step_definitions/untrusted_partitions.rb65
-rw-r--r--features/step_definitions/usb.rb217
-rw-r--r--features/support/config.rb55
-rw-r--r--features/support/helpers/exec_helper.rb20
-rw-r--r--features/support/helpers/firewall_helper.rb45
-rw-r--r--features/support/helpers/misc_helpers.rb46
-rw-r--r--features/support/helpers/sikuli_helper.rb16
-rw-r--r--features/support/helpers/sniffing_helper.rb (renamed from features/support/helpers/net_helper.rb)11
-rw-r--r--features/support/helpers/storage_helper.rb102
-rw-r--r--features/support/helpers/vm_helper.rb176
-rw-r--r--features/support/hooks.rb87
-rw-r--r--features/time_syncing.feature52
-rw-r--r--features/tor_bridges.feature52
-rw-r--r--features/tor_enforcement.feature72
-rw-r--r--features/tor_stream_isolation.feature60
-rw-r--r--features/torified_browsing.feature58
-rw-r--r--features/torified_git.feature36
-rw-r--r--features/torified_gnupg.feature5
-rw-r--r--features/torified_misc.feature32
-rw-r--r--features/totem.feature3
-rw-r--r--features/unsafe_browser.feature90
-rw-r--r--features/untrusted_partitions.feature50
-rw-r--r--features/usb_install.feature78
120 files changed, 2261 insertions, 622 deletions
diff --git a/features/apt.feature b/features/apt.feature
index e86d3c6..689a44e 100644
--- a/features/apt.feature
+++ b/features/apt.feature
@@ -7,7 +7,6 @@ Feature: Installing packages through APT
Background:
Given a computer
- And I capture all network traffic
And I start the computer
And the computer boots Tails
And I enable more Tails Greeter options
@@ -22,13 +21,13 @@ Feature: Installing packages through APT
Scenario: APT sources are configured correctly
Then the only hosts in APT sources are "ftp.us.debian.org,security.debian.org,backports.debian.org,deb.tails.boum.org,deb.torproject.org,mozilla.debian.net"
+ @check_tor_leaks
Scenario: Install packages using apt-get
When I update APT using apt-get
Then I should be able to install a package using apt-get
- And all Internet traffic has only flowed through Tor
+ @check_tor_leaks
Scenario: Install packages using Synaptic
When I start Synaptic
And I update APT using Synaptic
Then I should be able to install a package using Synaptic
- And all Internet traffic has only flowed through Tor
diff --git a/features/checks.feature b/features/checks.feature
index 277bdb9..4f1cc45 100644
--- a/features/checks.feature
+++ b/features/checks.feature
@@ -10,15 +10,30 @@ Feature: Various checks
Then AppArmor is enabled
And some AppArmor profiles are enforced
+ Scenario: GNOME Screenshot has a sane default save directory
+ Then GNOME Screenshot is configured to save files to the live user's home directory
+
+ Scenario: GNOME Screenshot takes a screenshot when the PRINTSCREEN key is pressed
+ Given there is no screenshot in the live user's home directory
+ When I press the "PRINTSCREEN" key
+ Then a screenshot is saved to the live user's home directory
+
Scenario: VirtualBox guest modules are available
When Tails has booted a 64-bit kernel
Then the VirtualBox guest modules are available
- Scenario: The shipped Tails signing key is up-to-date
+ Scenario: The shipped Tails signing key is up-to-date
+ Then the shipped Tails signing key will be valid for the next 3 months
+
+ Scenario: The Tails Debian repository key is up-to-date
+ Then the shipped Tails Debian repository key will be valid for the next 3 months
+
+ Scenario: The "Report an Error" launcher will open the support documentation
Given the network is plugged
And Tor is ready
And all notifications have disappeared
- Then the shipped Tails signing key is not outdated
+ When I double-click the Report an Error launcher on the desktop
+ Then I see "SupportDocumentation.png" after at most 90 seconds
Scenario: The live user is setup correctly
Then the live user has been setup by live-boot
@@ -33,6 +48,16 @@ Feature: Various checks
And the time has synced
And process "vidalia" is running within 30 seconds
+ Scenario: The 'Tor is ready' notification is shown when Tor has bootstrapped
+ Given the network is plugged
+ When I see the 'Tor is ready' notification
+ Then Tor is ready
+
+ Scenario: The tor process should be confined with Seccomp
+ Given the network is plugged
+ And Tor is ready
+ Then the running process "tor" is confined with Seccomp in filter mode
+
Scenario: No unexpected network services
When the network is plugged
And Tor is ready
diff --git a/features/config/defaults.yml b/features/config/defaults.yml
new file mode 100644
index 0000000..58fa44b
--- /dev/null
+++ b/features/config/defaults.yml
@@ -0,0 +1,34 @@
+DEBUG: false
+PAUSE_ON_FAIL: false
+SIKULI_RETRY_FINDFAILED: false
+TMP_DIR: "/tmp/TailsToaster"
+
+Unsafe_SSH_private_key: |
+ -----BEGIN RSA PRIVATE KEY-----
+ MIIEowIBAAKCAQEAvMUNgUUM/kyuo26m+Xw7igG6zgGFMFbS3u8m5StGsJOn7zLi
+ J8P5Mml/R+4tdOS6owVU4RaZTPsNZZK/ClYmOPhmNvJ04pVChk2DZ8AARg/TANj3
+ qjKs3D+MeKbk1bt6EsA55kgGsTUky5Ti8cc2Wna25jqjagIiyM822PGG9mmI6/zL
+ YR6QLUizNaciXrRM3Q4R4sQkEreVlHeonPEiGUs9zx0swCpLtPM5UIYte1PVHgkw
+ ePsU6vM8UqVTK/VwtLLgLanXnsMFuzq7DTAXPq49+XSFNq4JlxbEF6+PQXZvYZ5N
+ eW00Gq7NSpPP8uoHr6f1J+mMxxnM85jzYtRx+QIDAQABAoIBAA8Bs1MlhCTrP67q
+ awfGYo1UGd+qq0XugREL/hGV4SbEdkNDzkrO/46MaHv1aVOzo0q2b8r9Gu7NvoDm
+ q51Mv/kjdizEFZq1tvYqT1n+H4dyVpnopbe4E5nmy2oECokbQFchRPkTnMSVrvko
+ OupxpdaHPX8MBlW1GcLRBlE00j/gfK1SXX5rcxkF5EHVND1b6iHddTPearDbU8yr
+ wga1XO6WeohAYzqmGtMD0zk6lOk0LmnTNG6WvHiFTAc/0yTiKub6rNOIEMS/82+V
+ l437H0hKcIN/7/mf6FpqRNPJTuhOVFf+L4G/ZQ8zHoMGVIbhuTiIPqZ/KMu3NaUF
+ R634jckCgYEA+jJ31hom/d65LfxWPkmiSkNTEOTfjbfcgpfc7sS3enPsYnfnmn5L
+ O3JJzAKShSVP8NVuPN5Mg5FGp9QLKrN3kV6QWQ3EnqeW748DXMU6zKGJQ5wo7ZVm
+ w2DhJ/3PAuBTL/5X4mjPQL+dr86Aq2JBDC7LHJs40I8O7UbhnsdMxKcCgYEAwSXc
+ 3znAkAX8o2g37RiAl36HdONgxr2eaGK7OExp03pbKmoISw6bFbVpicBy6eTytn0A
+ 2PuFcBKJRfKrViHyiE8UfAJ31JbUaxpg4bFF6UEszN4CmgKS8fnwEe1aX0qSjvkE
+ NQSuhN5AfykXY/1WVIaWuC500uB7Ow6M16RDyF8CgYEAqFTeNYlg5Hs+Acd9SukF
+ rItBTuN92P5z+NUtyuNFQrjNuK5Nf68q9LL/Hag5ZiVldHZUddVmizpp3C6Y2MDo
+ WEDUQ2Y0/D1rGoAQ1hDIb7bbAEcHblmPSzJaKirkZV4B+g9Yl7bGghypfggkn6o6
+ c3TkKLnybrdhZpjC4a3bY48CgYBnWRYdD27c4Ycz/GDoaZLs/NQIFF5FGVL4cdPR
+ pPl/IdpEEKZNWwxaik5lWedjBZFlWe+pKrRUqmZvWhCZruJyUzYXwM5Tnz0b7epm
+ +Q76Z1hMaoKj27q65UyymvkfQey3ucCpic7D45RJNjiA1R5rbfSZqqnx6BGoIPn1
+ rLxkKwKBgDXiWeUKJCydj0NfHryGBkQvaDahDE3Yigcma63b8vMZPBrJSC4SGAHJ
+ NWema+bArbaF0rKVJpwvpkZWGcr6qRn94Ts0kJAzR+VIVTOjB9sVwdxjadwWHRs5
+ kKnpY0tnSF7hyVRwN7GOsNDJEaFjCW7k4+55D2ZNBy2iN3beW8CZ
+ -----END RSA PRIVATE KEY-----
+Unsafe_SSH_public_key: = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8xQ2BRQz+TK6jbqb5fDuKAbrOAYUwVtLe7yblK0awk6fvMuInw/kyaX9H7i105LqjBVThFplM+w1lkr8KViY4+GY28nTilUKGTYNnwABGD9MA2PeqMqzcP4x4puTVu3oSwDnmSAaxNSTLlOLxxzZadrbmOqNqAiLIzzbY8Yb2aYjr/MthHpAtSLM1pyJetEzdDhHixCQSt5WUd6ic8SIZSz3PHSzAKku08zlQhi17U9UeCTB4+xTq8zxSpVMr9XC0suAtqdeewwW7OrsNMBc+rj35dIU2rgmXFsQXr49Bdm9hnk15bTQars1Kk8/y6gevp/Un6YzHGczzmPNi1HH5 amnesia@amnesia"
diff --git a/features/firewall_leaks.feature b/features/firewall_leaks.feature
deleted file mode 100644
index 775c6e1..0000000
--- a/features/firewall_leaks.feature
+++ /dev/null
@@ -1,37 +0,0 @@
-@product
-Feature:
- As a Tails developer
- I want to ensure that the automated test suite detects firewall leaks reliably
-
- Background:
- Given a computer
- And I capture all network traffic
- And I start the computer
- And the computer boots Tails
- And I log in to a new session
- And Tor is ready
- And all notifications have disappeared
- And available upgrades have been checked
- And all Internet traffic has only flowed through Tor
- And I save the state so the background can be restored next scenario
-
- Scenario: Detecting IPv4 TCP leaks from the Unsafe Browser
- When I successfully start the Unsafe Browser
- And I open the address "https://check.torproject.org" in the Unsafe Browser
- And I see "UnsafeBrowserTorCheckFail.png" after at most 60 seconds
- Then the firewall leak detector has detected IPv4 TCP leaks
-
- Scenario: Detecting IPv4 TCP leaks of TCP DNS lookups
- Given I disable Tails' firewall
- When I do a TCP DNS lookup of "torproject.org"
- Then the firewall leak detector has detected IPv4 TCP leaks
-
- Scenario: Detecting IPv4 non-TCP leaks (UDP) of UDP DNS lookups
- Given I disable Tails' firewall
- When I do a UDP DNS lookup of "torproject.org"
- Then the firewall leak detector has detected IPv4 non-TCP leaks
-
- Scenario: Detecting IPv4 non-TCP (ICMP) leaks of ping
- Given I disable Tails' firewall
- When I send some ICMP pings
- Then the firewall leak detector has detected IPv4 non-TCP leaks
diff --git a/features/i2p.feature b/features/i2p.feature
index fc4cdf0..810c630 100644
--- a/features/i2p.feature
+++ b/features/i2p.feature
@@ -30,4 +30,5 @@ Feature: I2P
And the I2P Browser sudo rules are enabled
And the I2P firewall rules are enabled
When I start the I2P Browser through the GNOME menu
- Then I see "I2P_router_console.png" after at most 60 seconds
+ Then I see "I2P_router_console.png" after at most 120 seconds
+ And the I2P Browser uses all expected TBB shared libraries
diff --git a/features/images/DesktopReportAnError.png b/features/images/DesktopReportAnError.png
new file mode 100644
index 0000000..95863a1
--- /dev/null
+++ b/features/images/DesktopReportAnError.png
Binary files differ
diff --git a/features/images/DesktopTailsDocumentationIcon.png b/features/images/DesktopTailsDocumentationIcon.png
new file mode 100644
index 0000000..5128b60
--- /dev/null
+++ b/features/images/DesktopTailsDocumentationIcon.png
Binary files differ
diff --git a/features/images/GeditCopy.png b/features/images/GeditCopy.png
new file mode 100644
index 0000000..ef93b7e
--- /dev/null
+++ b/features/images/GeditCopy.png
Binary files differ
diff --git a/features/images/GeditEdit.png b/features/images/GeditEdit.png
new file mode 100644
index 0000000..5b42d64
--- /dev/null
+++ b/features/images/GeditEdit.png
Binary files differ
diff --git a/features/images/GeditNewDocument.png b/features/images/GeditNewDocument.png
new file mode 100644
index 0000000..000326d
--- /dev/null
+++ b/features/images/GeditNewDocument.png
Binary files differ
diff --git a/features/images/GeditPaste.png b/features/images/GeditPaste.png
new file mode 100644
index 0000000..7043543
--- /dev/null
+++ b/features/images/GeditPaste.png
Binary files differ
diff --git a/features/images/GeditSelectAll.png b/features/images/GeditSelectAll.png
new file mode 100644
index 0000000..276e6ea
--- /dev/null
+++ b/features/images/GeditSelectAll.png
Binary files differ
diff --git a/features/images/GitSSHFingerprint.png b/features/images/GitSSHFingerprint.png
new file mode 100644
index 0000000..7ae087b
--- /dev/null
+++ b/features/images/GitSSHFingerprint.png
Binary files differ
diff --git a/features/images/GnomeApplicationsGobby.png b/features/images/GnomeApplicationsGobby.png
new file mode 100644
index 0000000..dced881
--- /dev/null
+++ b/features/images/GnomeApplicationsGobby.png
Binary files differ
diff --git a/features/images/GnomeCloseButton.png b/features/images/GnomeCloseButton.png
new file mode 100644
index 0000000..bf1efe1
--- /dev/null
+++ b/features/images/GnomeCloseButton.png
Binary files differ
diff --git a/features/images/GnomePlaces.png b/features/images/GnomePlaces.png
new file mode 100644
index 0000000..98036cc
--- /dev/null
+++ b/features/images/GnomePlaces.png
Binary files differ
diff --git a/features/images/GnomePlacesWithoutTorBrowserPersistent.png b/features/images/GnomePlacesWithoutTorBrowserPersistent.png
new file mode 100644
index 0000000..710cb5d
--- /dev/null
+++ b/features/images/GnomePlacesWithoutTorBrowserPersistent.png
Binary files differ
diff --git a/features/images/GobbyConnectPrompt.png b/features/images/GobbyConnectPrompt.png
new file mode 100644
index 0000000..03c358b
--- /dev/null
+++ b/features/images/GobbyConnectPrompt.png
Binary files differ
diff --git a/features/images/GobbyConnectionComplete.png b/features/images/GobbyConnectionComplete.png
new file mode 100644
index 0000000..db904c7
--- /dev/null
+++ b/features/images/GobbyConnectionComplete.png
Binary files differ
diff --git a/features/images/GobbyWelcomePrompt.png b/features/images/GobbyWelcomePrompt.png
new file mode 100644
index 0000000..861a5f1
--- /dev/null
+++ b/features/images/GobbyWelcomePrompt.png
Binary files differ
diff --git a/features/images/GobbyWindow.png b/features/images/GobbyWindow.png
new file mode 100644
index 0000000..873f391
--- /dev/null
+++ b/features/images/GobbyWindow.png
Binary files differ
diff --git a/features/images/GpgAppletDecryptVerify.png b/features/images/GpgAppletDecryptVerify.png
new file mode 100644
index 0000000..59594d8
--- /dev/null
+++ b/features/images/GpgAppletDecryptVerify.png
Binary files differ
diff --git a/features/images/GpgAppletEncryptPassphrase.png b/features/images/GpgAppletEncryptPassphrase.png
new file mode 100644
index 0000000..d8ffe11
--- /dev/null
+++ b/features/images/GpgAppletEncryptPassphrase.png
Binary files differ
diff --git a/features/images/GpgAppletSignEncrypt.png b/features/images/GpgAppletSignEncrypt.png
new file mode 100644
index 0000000..e8e42cd
--- /dev/null
+++ b/features/images/GpgAppletSignEncrypt.png
Binary files differ
diff --git a/features/images/GtkTorBrowserPersistentBookmark.png b/features/images/GtkTorBrowserPersistentBookmark.png
new file mode 100644
index 0000000..aa6e936
--- /dev/null
+++ b/features/images/GtkTorBrowserPersistentBookmark.png
Binary files differ
diff --git a/features/images/GtkTorBrowserPersistentBookmarkSelected.png b/features/images/GtkTorBrowserPersistentBookmarkSelected.png
new file mode 100644
index 0000000..f084ba7
--- /dev/null
+++ b/features/images/GtkTorBrowserPersistentBookmarkSelected.png
Binary files differ
diff --git a/features/images/KeyImportedNotification.png b/features/images/KeyImportedNotification.png
new file mode 100644
index 0000000..f3e0718
--- /dev/null
+++ b/features/images/KeyImportedNotification.png
Binary files differ
diff --git a/features/images/OpenWithImportKey.png b/features/images/OpenWithImportKey.png
new file mode 100644
index 0000000..5cca550
--- /dev/null
+++ b/features/images/OpenWithImportKey.png
Binary files differ
diff --git a/features/images/PidginTailsRoadmapUrl.png b/features/images/PidginTailsRoadmapUrl.png
new file mode 100644
index 0000000..98ece01
--- /dev/null
+++ b/features/images/PidginTailsRoadmapUrl.png
Binary files differ
diff --git a/features/images/EvincePrintToFile.png b/features/images/PrintToFile.png
index 2175d01..2175d01 100644
--- a/features/images/EvincePrintToFile.png
+++ b/features/images/PrintToFile.png
Binary files differ
diff --git a/features/images/SSHAuthVerification.png b/features/images/SSHAuthVerification.png
new file mode 100644
index 0000000..abfa39a
--- /dev/null
+++ b/features/images/SSHAuthVerification.png
Binary files differ
diff --git a/features/images/SupportDocumentation.png b/features/images/SupportDocumentation.png
new file mode 100644
index 0000000..6275f01
--- /dev/null
+++ b/features/images/SupportDocumentation.png
Binary files differ
diff --git a/features/images/SynapticApply.png b/features/images/SynapticApply.png
new file mode 100644
index 0000000..8140848
--- /dev/null
+++ b/features/images/SynapticApply.png
Binary files differ
diff --git a/features/images/SynapticCowsayMarked.png b/features/images/SynapticCowsayMarked.png
new file mode 100644
index 0000000..b6d1a30
--- /dev/null
+++ b/features/images/SynapticCowsayMarked.png
Binary files differ
diff --git a/features/images/SynapticCowsaySearchResultSelected.png b/features/images/SynapticCowsaySearchResultSelected.png
new file mode 100644
index 0000000..65156bf
--- /dev/null
+++ b/features/images/SynapticCowsaySearchResultSelected.png
Binary files differ
diff --git a/features/images/SynapticPackageList.png b/features/images/SynapticPackageList.png
new file mode 100644
index 0000000..cf0cbc7
--- /dev/null
+++ b/features/images/SynapticPackageList.png
Binary files differ
diff --git a/features/images/TailsGreeterTorConf.png b/features/images/TailsGreeterTorConf.png
new file mode 100644
index 0000000..b2464ef
--- /dev/null
+++ b/features/images/TailsGreeterTorConf.png
Binary files differ
diff --git a/features/images/TailsOfflineDocHomepage.png b/features/images/TailsOfflineDocHomepage.png
new file mode 100644
index 0000000..95d5ea6
--- /dev/null
+++ b/features/images/TailsOfflineDocHomepage.png
Binary files differ
diff --git a/features/images/TorBrowserAmnesicFilesBookmark.png b/features/images/TorBrowserAmnesicFilesBookmark.png
new file mode 100644
index 0000000..c256450
--- /dev/null
+++ b/features/images/TorBrowserAmnesicFilesBookmark.png
Binary files differ
diff --git a/features/images/TorBrowserBlockedVideo.png b/features/images/TorBrowserBlockedVideo.png
new file mode 100644
index 0000000..a4916cc
--- /dev/null
+++ b/features/images/TorBrowserBlockedVideo.png
Binary files differ
diff --git a/features/images/TorBrowserCouldNotReadTheContentsOfWarning.png b/features/images/TorBrowserCouldNotReadTheContentsOfWarning.png
new file mode 100644
index 0000000..20beef8
--- /dev/null
+++ b/features/images/TorBrowserCouldNotReadTheContentsOfWarning.png
Binary files differ
diff --git a/features/images/TorBrowserHtml5PlayButton.png b/features/images/TorBrowserHtml5PlayButton.png
new file mode 100644
index 0000000..11d0eaf
--- /dev/null
+++ b/features/images/TorBrowserHtml5PlayButton.png
Binary files differ
diff --git a/features/images/TorBrowserNoScriptTemporarilyAllowDialog.png b/features/images/TorBrowserNoScriptTemporarilyAllowDialog.png
new file mode 100644
index 0000000..45b60bf
--- /dev/null
+++ b/features/images/TorBrowserNoScriptTemporarilyAllowDialog.png
Binary files differ
diff --git a/features/images/TorBrowserOkButton.png b/features/images/TorBrowserOkButton.png
new file mode 100644
index 0000000..662d90f
--- /dev/null
+++ b/features/images/TorBrowserOkButton.png
Binary files differ
diff --git a/features/images/TorBrowserPersistentFilesBookmark.png b/features/images/TorBrowserPersistentFilesBookmark.png
new file mode 100644
index 0000000..47dea26
--- /dev/null
+++ b/features/images/TorBrowserPersistentFilesBookmark.png
Binary files differ
diff --git a/features/images/TorBrowserPrintDialog.png b/features/images/TorBrowserPrintDialog.png
new file mode 100644
index 0000000..77404f3
--- /dev/null
+++ b/features/images/TorBrowserPrintDialog.png
Binary files differ
diff --git a/features/images/TorBrowserPrintOutputFile.png b/features/images/TorBrowserPrintOutputFile.png
new file mode 100644
index 0000000..e940ad9
--- /dev/null
+++ b/features/images/TorBrowserPrintOutputFile.png
Binary files differ
diff --git a/features/images/TorBrowserPrintOutputFileSelected.png b/features/images/TorBrowserPrintOutputFileSelected.png
new file mode 100644
index 0000000..dc625ad
--- /dev/null
+++ b/features/images/TorBrowserPrintOutputFileSelected.png
Binary files differ
diff --git a/features/images/TorBrowserSampleRemoteHTML5VideoFrame.png b/features/images/TorBrowserSampleRemoteHTML5VideoFrame.png
new file mode 100644
index 0000000..2ba8ae7
--- /dev/null
+++ b/features/images/TorBrowserSampleRemoteHTML5VideoFrame.png
Binary files differ
diff --git a/features/images/TorBrowserSampleRemoteWebMVideoFrame.png b/features/images/TorBrowserSampleRemoteWebMVideoFrame.png
new file mode 100644
index 0000000..36e7301
--- /dev/null
+++ b/features/images/TorBrowserSampleRemoteWebMVideoFrame.png
Binary files differ
diff --git a/features/images/TorBrowserSaveOutputFileSelected.png b/features/images/TorBrowserSaveOutputFileSelected.png
new file mode 100644
index 0000000..8addb17
--- /dev/null
+++ b/features/images/TorBrowserSaveOutputFileSelected.png
Binary files differ
diff --git a/features/images/TorBrowserSavedStartupPage.png b/features/images/TorBrowserSavedStartupPage.png
new file mode 100644
index 0000000..17cbd88
--- /dev/null
+++ b/features/images/TorBrowserSavedStartupPage.png
Binary files differ
diff --git a/features/images/TorBrowserSynapticManual.png b/features/images/TorBrowserSynapticManual.png
new file mode 100644
index 0000000..f8ffa3e
--- /dev/null
+++ b/features/images/TorBrowserSynapticManual.png
Binary files differ
diff --git a/features/images/TorBrowserTailsRoadmap.png b/features/images/TorBrowserTailsRoadmap.png
new file mode 100644
index 0000000..80d8976
--- /dev/null
+++ b/features/images/TorBrowserTailsRoadmap.png
Binary files differ
diff --git a/features/images/TorBrowserUnableToOpen.png b/features/images/TorBrowserUnableToOpen.png
new file mode 100644
index 0000000..8534ff3
--- /dev/null
+++ b/features/images/TorBrowserUnableToOpen.png
Binary files differ
diff --git a/features/images/TorBrowserWarningDialogOkButton.png b/features/images/TorBrowserWarningDialogOkButton.png
new file mode 100644
index 0000000..59c0102
--- /dev/null
+++ b/features/images/TorBrowserWarningDialogOkButton.png
Binary files differ
diff --git a/features/images/TorLauncherBridgeList.png b/features/images/TorLauncherBridgeList.png
new file mode 100644
index 0000000..cb28a1e
--- /dev/null
+++ b/features/images/TorLauncherBridgeList.png
Binary files differ
diff --git a/features/images/TorLauncherBridgePrompt.png b/features/images/TorLauncherBridgePrompt.png
new file mode 100644
index 0000000..132e653
--- /dev/null
+++ b/features/images/TorLauncherBridgePrompt.png
Binary files differ
diff --git a/features/images/TorLauncherConfigureButton.png b/features/images/TorLauncherConfigureButton.png
new file mode 100644
index 0000000..21a5d33
--- /dev/null
+++ b/features/images/TorLauncherConfigureButton.png
Binary files differ
diff --git a/features/images/TorLauncherConnectingWindow.png b/features/images/TorLauncherConnectingWindow.png
new file mode 100644
index 0000000..e28fae7
--- /dev/null
+++ b/features/images/TorLauncherConnectingWindow.png
Binary files differ
diff --git a/features/images/TorLauncherFinishButton.png b/features/images/TorLauncherFinishButton.png
new file mode 100644
index 0000000..55ce3cb
--- /dev/null
+++ b/features/images/TorLauncherFinishButton.png
Binary files differ
diff --git a/features/images/TorLauncherNextButton.png b/features/images/TorLauncherNextButton.png
new file mode 100644
index 0000000..d8bc684
--- /dev/null
+++ b/features/images/TorLauncherNextButton.png
Binary files differ
diff --git a/features/images/TorLauncherWindow.png b/features/images/TorLauncherWindow.png
new file mode 100644
index 0000000..7dbc417
--- /dev/null
+++ b/features/images/TorLauncherWindow.png
Binary files differ
diff --git a/features/images/TorLauncherYesRadioOption.png b/features/images/TorLauncherYesRadioOption.png
new file mode 100644
index 0000000..4441862
--- /dev/null
+++ b/features/images/TorLauncherYesRadioOption.png
Binary files differ
diff --git a/features/images/UnsafeBrowserAddressBar.png b/features/images/UnsafeBrowserAddressBar.png
new file mode 120000
index 0000000..af820bd
--- /dev/null
+++ b/features/images/UnsafeBrowserAddressBar.png
@@ -0,0 +1 @@
+TorBrowserAddressBar.png \ No newline at end of file
diff --git a/features/images/UnsafeBrowserDNSError.png b/features/images/UnsafeBrowserDNSError.png
new file mode 100644
index 0000000..cdf3dff
--- /dev/null
+++ b/features/images/UnsafeBrowserDNSError.png
Binary files differ
diff --git a/features/images/UnsafeBrowserExportBookmarksButton.png b/features/images/UnsafeBrowserExportBookmarksButton.png
new file mode 100644
index 0000000..a9e8c62
--- /dev/null
+++ b/features/images/UnsafeBrowserExportBookmarksButton.png
Binary files differ
diff --git a/features/images/UnsafeBrowserExportBookmarksMenuEntry.png b/features/images/UnsafeBrowserExportBookmarksMenuEntry.png
new file mode 100644
index 0000000..fdea806
--- /dev/null
+++ b/features/images/UnsafeBrowserExportBookmarksMenuEntry.png
Binary files differ
diff --git a/features/images/UnsafeBrowserExportBookmarksSavePrompt.png b/features/images/UnsafeBrowserExportBookmarksSavePrompt.png
new file mode 100644
index 0000000..01ba6d4
--- /dev/null
+++ b/features/images/UnsafeBrowserExportBookmarksSavePrompt.png
Binary files differ
diff --git a/features/images/UnsafeBrowserNewTabButton.png b/features/images/UnsafeBrowserNewTabButton.png
new file mode 100644
index 0000000..6fa2f75
--- /dev/null
+++ b/features/images/UnsafeBrowserNewTabButton.png
Binary files differ
diff --git a/features/images/UnsafeBrowserNoAddons.png b/features/images/UnsafeBrowserNoAddons.png
new file mode 100644
index 0000000..5999220
--- /dev/null
+++ b/features/images/UnsafeBrowserNoAddons.png
Binary files differ
diff --git a/features/images/UnsafeBrowserNoProxySelected.png b/features/images/UnsafeBrowserNoProxySelected.png
new file mode 100644
index 0000000..8368cd9
--- /dev/null
+++ b/features/images/UnsafeBrowserNoProxySelected.png
Binary files differ
diff --git a/features/images/UnsafeBrowserStartVerification.png b/features/images/UnsafeBrowserStartVerification.png
index 9ef264a..7e87ee4 100644
--- a/features/images/UnsafeBrowserStartVerification.png
+++ b/features/images/UnsafeBrowserStartVerification.png
Binary files differ
diff --git a/features/images/UnsafeBrowserTorCheckFail.png b/features/images/UnsafeBrowserTorCheckFail.png
index 33cd820..635c2e3 100644
--- a/features/images/UnsafeBrowserTorCheckFail.png
+++ b/features/images/UnsafeBrowserTorCheckFail.png
Binary files differ
diff --git a/features/images/WindowsApplicationsTails.png b/features/images/WindowsApplicationsTails.png
new file mode 100644
index 0000000..1527559
--- /dev/null
+++ b/features/images/WindowsApplicationsTails.png
Binary files differ
diff --git a/features/pidgin.feature b/features/pidgin.feature
index 51d4a77..83330ce 100644
--- a/features/pidgin.feature
+++ b/features/pidgin.feature
@@ -9,11 +9,11 @@ Feature: Chatting anonymously using Pidgin
Background:
Given a computer
- And I capture all network traffic
When I start Tails from DVD and I login
Then Pidgin has the expected accounts configured with random nicknames
And I save the state so the background can be restored next scenario
+ @check_tor_leaks
Scenario: Connecting to the #tails IRC channel with the pre-configured account
When I start Pidgin through the GNOME menu
Then I see Pidgin's account manager window
@@ -21,7 +21,11 @@ Feature: Chatting anonymously using Pidgin
And I close Pidgin's account manager window
Then Pidgin successfully connects to the "irc.oftc.net" account
And I can join the "#tails" channel on "irc.oftc.net"
- And all Internet traffic has only flowed through Tor
+ When I type "/topic"
+ And I press the "ENTER" key
+ Then I see the Tails roadmap URL
+ When I click on the Tails roadmap URL
+ Then the Tor Browser has started and loaded the Tails roadmap
Scenario: Adding a certificate to Pidgin
And I start Pidgin through the GNOME menu
@@ -35,7 +39,7 @@ Feature: Chatting anonymously using Pidgin
And I close Pidgin's account manager window
Then I cannot add a certificate from the "/home/amnesia/.gnupg" directory to Pidgin
- @keep_volumes
+ @keep_volumes @check_tor_leaks
Scenario: Using a persistent Pidgin configuration
Given the USB drive "current" contains Tails with persistence configured and password "asdf"
And a computer
@@ -47,7 +51,6 @@ Feature: Chatting anonymously using Pidgin
# And I take note of the OTR key for Pidgin's "irc.oftc.net" account
And I shutdown Tails and wait for the computer to power off
Given a computer
- And I capture all network traffic
And I start Tails from USB drive "current" and I login with persistence password "asdf"
And Pidgin has the expected persistent accounts configured
# And Pidgin has the expected persistent OTR keys
@@ -57,7 +60,6 @@ Feature: Chatting anonymously using Pidgin
And I close Pidgin's account manager window
Then Pidgin successfully connects to the "irc.oftc.net" account
And I can join the "#tails" channel on "irc.oftc.net"
- And all Internet traffic has only flowed through Tor
# Exercise Pidgin AppArmor profile with persistence enabled.
# This should really be in dedicated scenarios, but it would be
# too costly to set up the virtual USB drive with persistence more
diff --git a/features/po.feature b/features/po.feature
new file mode 100644
index 0000000..91b8daa
--- /dev/null
+++ b/features/po.feature
@@ -0,0 +1,8 @@
+@source
+Feature: check PO files
+ As a Tails developer, when I build Tails, I want to make sure
+ the PO files in use are correct.
+
+ Scenario: check all PO files
+ Given I am in the Git branch being tested
+ Then all the PO files should be correct
diff --git a/features/scripts/vm-execute b/features/scripts/vm-execute
new file mode 100755
index 0000000..f1c285f
--- /dev/null
+++ b/features/scripts/vm-execute
@@ -0,0 +1,52 @@
+#!/usr/bin/env ruby
+
+require 'optparse'
+begin
+ require "#{`git rev-parse --show-toplevel`.chomp}/features/support/helpers/exec_helper.rb"
+rescue LoadError => e
+ raise "This script must be run from within Tails' Git directory."
+end
+$config = Hash.new
+$config["DEBUG"] = false
+
+class FakeVM
+ def get_remote_shell_port
+ 1337
+ end
+end
+
+cmd_opts = {
+ :spawn => false,
+ :user => "root"
+}
+
+opt_parser = OptionParser.new do |opts|
+ opts.banner = "Usage: features/scripts/vm-execute [opts] COMMAND"
+ opts.separator ""
+ opts.separator "Runs commands in the VM guest being tested. This script " \
+ "must be run from within Tails' Git directory."
+ opts.separator ""
+ opts.separator "Options:"
+
+ opts.on("-h", "--help", "Show this message") do
+ puts opts
+ exit
+ end
+
+ opts.on("-u", "--user USER", "Run command as USER") do |user|
+ cmd_opts[:user] = user
+ end
+
+ opts.on("-t", "--type TYPE",
+ "Run command as blocking with 'call' (default) or " \
+ "non-blocking with 'spawn'") do |type|
+ cmd_opts[:type] = (type == "spawn")
+ end
+end
+opt_parser.parse!(ARGV)
+cmd = ARGV.join(" ")
+c = VMCommand.new(FakeVM.new, cmd, cmd_opts)
+puts "Return status: #{c.returncode}"
+puts "STDOUT:\n#{c.stdout}"
+puts "STDERR:\n#{c.stderr}"
+exit c.returncode
diff --git a/features/step_definitions/apt.rb b/features/step_definitions/apt.rb
index 6f2e39a..8be9e6d 100644
--- a/features/step_definitions/apt.rb
+++ b/features/step_definitions/apt.rb
@@ -15,11 +15,8 @@ end
When /^I update APT using apt-get$/ do
next if @skip_steps_while_restoring_background
Timeout::timeout(30*60) do
- cmd = @vm.execute("echo #{@sudo_password} | " +
- "sudo -S apt-get update", $live_user)
- if !cmd.success?
- STDERR.puts cmd.stderr
- end
+ @vm.execute_successfully("echo #{@sudo_password} | " +
+ "sudo -S apt-get update", LIVE_USER)
end
end
@@ -27,11 +24,8 @@ Then /^I should be able to install a package using apt-get$/ do
next if @skip_steps_while_restoring_background
package = "cowsay"
Timeout::timeout(120) do
- cmd = @vm.execute("echo #{@sudo_password} | " +
- "sudo -S apt-get install #{package}", $live_user)
- if !cmd.success?
- STDERR.puts cmd.stderr
- end
+ @vm.execute_successfully("echo #{@sudo_password} | " +
+ "sudo -S apt-get install #{package}", LIVE_USER)
end
step "package \"#{package}\" is installed"
end
@@ -47,23 +41,21 @@ When /^I update APT using Synaptic$/ do
@screen.find('SynapticReloadPrompt.png')
}
@screen.waitVanish('SynapticReloadPrompt.png', 30*60)
+ # After this next image is displayed, the GUI should be responsive.
+ @screen.wait('SynapticPackageList.png', 30)
end
Then /^I should be able to install a package using Synaptic$/ do
next if @skip_steps_while_restoring_background
package = "cowsay"
- # We do this after a Reload, so the interface will be frozen until
- # the package list has been loaded
- try_for(60, :msg => "Failed to open the Synaptic 'Find' window") {
- @screen.type("f", Sikuli::KeyModifier.CTRL) # Find key
- @screen.find('SynapticSearch.png')
- }
+ @screen.type("f", Sikuli::KeyModifier.CTRL) # Find key
+ @screen.wait_and_click('SynapticSearch.png', 10)
@screen.type(package + Sikuli::Key.ENTER)
@screen.wait_and_click('SynapticCowsaySearchResult.png', 20)
- sleep 5
+ @screen.wait('SynapticCowsaySearchResultSelected.png', 20)
@screen.type("i", Sikuli::KeyModifier.CTRL) # Mark for installation
- sleep 5
- @screen.type("p", Sikuli::KeyModifier.CTRL) # Apply
+ @screen.wait('SynapticCowsayMarked.png', 10)
+ @screen.wait_and_click('SynapticApply.png', 10)
@screen.wait('SynapticApplyPrompt.png', 60)
@screen.type("a", Sikuli::KeyModifier.ALT) # Verify apply
@screen.wait('SynapticChangesAppliedPrompt.png', 120)
diff --git a/features/step_definitions/checks.rb b/features/step_definitions/checks.rb
index 76cfe67..4668fbf 100644
--- a/features/step_definitions/checks.rb
+++ b/features/step_definitions/checks.rb
@@ -1,25 +1,25 @@
-Then /^the shipped Tails signing key is not outdated$/ do
- # "old" here is w.r.t. the one we fetch from Tails' website
+Then /^the shipped Tails (signing|Debian repository) key will be valid for the next (\d+) months$/ do |key_type, max_months|
next if @skip_steps_while_restoring_background
- sig_key_fingerprint = "0D24B36AA9A2A651787876451202821CBE2CD9C1"
- fresh_sig_key = "/tmp/tails-signing.key"
- tmp_keyring = "/tmp/tmp-keyring.gpg"
- key_url = "https://tails.boum.org/tails-signing.key"
- @vm.execute("curl --silent --socks5-hostname localhost:9062 " +
- "#{key_url} -o #{fresh_sig_key}", $live_user)
- @vm.execute("gpg --batch --no-default-keyring --keyring #{tmp_keyring} " +
- "--import #{fresh_sig_key}", $live_user)
- fresh_sig_key_info =
- @vm.execute("gpg --batch --no-default-keyring --keyring #{tmp_keyring} " +
- "--list-key #{sig_key_fingerprint}", $live_user).stdout
- shipped_sig_key_info = @vm.execute("gpg --batch --list-key #{sig_key_fingerprint}",
- $live_user).stdout
- assert_equal(fresh_sig_key_info, shipped_sig_key_info,
- "The Tails signing key shipped inside Tails is outdated:\n" +
- "Shipped key:\n" +
- shipped_sig_key_info +
- "Newly fetched key from #{key_url}:\n" +
- fresh_sig_key_info)
+ if key_type == 'signing'
+ sig_key_fingerprint = "0D24B36AA9A2A651787876451202821CBE2CD9C1"
+ cmd = 'gpg'
+ user = LIVE_USER
+ elsif key_type == 'Debian repository'
+ sig_key_fingerprint = "221F9A3C6FA3E09E182E060BC7988EA7A358D82E"
+ cmd = 'apt-key adv'
+ user = 'root'
+ else
+ raise 'Unknown key type #{key_type}'
+ end
+ shipped_sig_key_info = @vm.execute_successfully("#{cmd} --batch --list-key #{sig_key_fingerprint}", user).stdout
+ expiration_date = Date.parse(/\[expires: ([0-9-]*)\]/.match(shipped_sig_key_info)[1])
+ assert((expiration_date << max_months.to_i) > DateTime.now,
+ "The shipped signing key will expire within the next #{max_months} months.")
+end
+
+Then /^I double-click the Report an Error launcher on the desktop$/ do
+ next if @skip_steps_while_restoring_background
+ @screen.wait_and_double_click('DesktopReportAnError.png', 30)
end
Then /^the live user has been setup by live\-boot$/ do
@@ -28,13 +28,13 @@ Then /^the live user has been setup by live\-boot$/ do
"live-boot failed its user-setup")
actual_username = @vm.execute(". /etc/live/config/username.conf; " +
"echo $LIVE_USERNAME").stdout.chomp
- assert_equal($live_user, actual_username)
+ assert_equal(LIVE_USER, actual_username)
end
Then /^the live user is a member of only its own group and "(.*?)"$/ do |groups|
next if @skip_steps_while_restoring_background
- expected_groups = groups.split(" ") << $live_user
- actual_groups = @vm.execute("groups #{$live_user}").stdout.chomp.sub(/^#{$live_user} : /, "").split(" ")
+ expected_groups = groups.split(" ") << LIVE_USER
+ actual_groups = @vm.execute("groups #{LIVE_USER}").stdout.chomp.sub(/^#{LIVE_USER} : /, "").split(" ")
unexpected = actual_groups - expected_groups
missing = expected_groups - actual_groups
assert_equal(0, unexpected.size,
@@ -45,12 +45,12 @@ end
Then /^the live user owns its home dir and it has normal permissions$/ do
next if @skip_steps_while_restoring_background
- home = "/home/#{$live_user}"
+ home = "/home/#{LIVE_USER}"
assert(@vm.execute("test -d #{home}").success?,
"The live user's home doesn't exist or is not a directory")
owner = @vm.execute("stat -c %U:%G #{home}").stdout.chomp
perms = @vm.execute("stat -c %a #{home}").stdout.chomp
- assert_equal("#{$live_user}:#{$live_user}", owner)
+ assert_equal("#{LIVE_USER}:#{LIVE_USER}", owner)
assert_equal("700", perms)
end
@@ -79,8 +79,8 @@ Then /^no unexpected services are listening for network connections$/ do
proc = splitted[proc_index].split("/")[1]
# Services listening on loopback is not a threat
if /127(\.[[:digit:]]{1,3}){3}/.match(laddr).nil?
- if $services_expected_on_all_ifaces.include? [proc, laddr, lport] or
- $services_expected_on_all_ifaces.include? [proc, laddr, "*"]
+ if SERVICES_EXPECTED_ON_ALL_IFACES.include? [proc, laddr, lport] or
+ SERVICES_EXPECTED_ON_ALL_IFACES.include? [proc, laddr, "*"]
puts "Service '#{proc}' is listening on #{laddr}:#{lport} " +
"but has an exception"
else
@@ -96,6 +96,31 @@ When /^Tails has booted a 64-bit kernel$/ do
"Tails has not booted a 64-bit kernel.")
end
+Then /^GNOME Screenshot is configured to save files to the live user's home directory$/ do
+ next if @skip_steps_while_restoring_background
+ home = "/home/#{LIVE_USER}"
+ save_path = @vm.execute_successfully(
+ "gsettings get org.gnome.gnome-screenshot auto-save-directory",
+ LIVE_USER).stdout.chomp.tr("'","")
+ assert_equal("file://#{home}", save_path,
+ "The GNOME screenshot auto-save-directory is not set correctly.")
+end
+
+Then /^there is no screenshot in the live user's home directory$/ do
+ next if @skip_steps_while_restoring_background
+ home = "/home/#{LIVE_USER}"
+ assert(@vm.execute("find '#{home}' -name 'Screenshot*.png' -maxdepth 1").stdout.empty?,
+ "Existing screenshots were found in the live user's home directory.")
+end
+
+Then /^a screenshot is saved to the live user's home directory$/ do
+ next if @skip_steps_while_restoring_background
+ home = "/home/#{LIVE_USER}"
+ try_for(3, :msg=> "No screenshot was created in #{home}") {
+ !@vm.execute("find '#{home}' -name 'Screenshot*.png' -maxdepth 1").stdout.empty?
+ }
+end
+
Then /^the VirtualBox guest modules are available$/ do
next if @skip_steps_while_restoring_background
assert(@vm.execute("modinfo vboxguest").success?,
@@ -108,26 +133,26 @@ end
Given /^I setup a filesystem share containing a sample PDF$/ do
next if @skip_steps_while_restoring_background
- @vm.add_share($misc_files_dir, shared_pdf_dir_on_guest)
+ @vm.add_share(MISC_FILES_DIR, shared_pdf_dir_on_guest)
end
Then /^MAT can clean some sample PDF file$/ do
next if @skip_steps_while_restoring_background
- for pdf_on_host in Dir.glob("#{$misc_files_dir}/*.pdf") do
+ for pdf_on_host in Dir.glob("#{MISC_FILES_DIR}/*.pdf") do
pdf_name = File.basename(pdf_on_host)
- pdf_on_guest = "/home/#{$live_user}/#{pdf_name}"
- step "I copy \"#{shared_pdf_dir_on_guest}/#{pdf_name}\" to \"#{pdf_on_guest}\" as user \"#{$live_user}\""
+ pdf_on_guest = "/home/#{LIVE_USER}/#{pdf_name}"
+ step "I copy \"#{shared_pdf_dir_on_guest}/#{pdf_name}\" to \"#{pdf_on_guest}\" as user \"#{LIVE_USER}\""
@vm.execute("mat --display '#{pdf_on_guest}'",
- $live_user).stdout
+ LIVE_USER).stdout
check_before = @vm.execute("mat --check '#{pdf_on_guest}'",
- $live_user).stdout
+ LIVE_USER).stdout
if check_before.include?("#{pdf_on_guest} is clean")
STDERR.puts "warning: '#{pdf_on_host}' is already clean so it is a " +
"bad candidate for testing MAT"
end
- @vm.execute("mat '#{pdf_on_guest}'", $live_user)
+ @vm.execute("mat '#{pdf_on_guest}'", LIVE_USER)
check_after = @vm.execute("mat --check '#{pdf_on_guest}'",
- $live_user).stdout
+ LIVE_USER).stdout
assert(check_after.include?("#{pdf_on_guest} is clean"),
"MAT failed to clean '#{pdf_on_host}'")
end
@@ -141,3 +166,22 @@ Then /^some AppArmor profiles are enforced$/ do
assert(@vm.execute("aa-status --enforced").stdout.chomp.to_i > 0,
"No AppArmor profile is enforced")
end
+
+def get_seccomp_status(process)
+ assert(@vm.has_process?(process), "Process #{process} not running.")
+ pid = @vm.pidof(process)[0]
+ status = @vm.file_content("/proc/#{pid}/status")
+ return status.match(/^Seccomp:\s+([0-9])/)[1].chomp.to_i
+end
+
+Then /^the running process "(.+)" is confined with Seccomp in (filter|strict) mode$/ do |process,mode|
+ next if @skip_steps_while_restoring_background
+ 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
+end
diff --git a/features/step_definitions/common_steps.rb b/features/step_definitions/common_steps.rb
index 694291c..12cbc9c 100644
--- a/features/step_definitions/common_steps.rb
+++ b/features/step_definitions/common_steps.rb
@@ -56,14 +56,16 @@ def restore_background
@vm.host_to_guest_time_sync
@vm.execute("service tor start")
wait_until_tor_is_working
- @vm.spawn("/usr/local/sbin/restart-vidalia")
+ @vm.spawn("restart-vidalia")
end
+ else
+ @vm.host_to_guest_time_sync
end
end
Given /^a computer$/ do
- @vm.destroy if @vm
- @vm = VM.new($vm_xml_path, $x_display)
+ @vm.destroy_and_undefine if @vm
+ @vm = VM.new($virt, VM_XML_PATH, $vmnet, $vmstorage, DISPLAY)
end
Given /^the computer has (\d+) ([[:alpha:]]+) of RAM$/ do |size, unit|
@@ -73,7 +75,7 @@ end
Given /^the computer is set to boot from the Tails DVD$/ do
next if @skip_steps_while_restoring_background
- @vm.set_cdrom_boot($tails_iso)
+ @vm.set_cdrom_boot(TAILS_ISO)
end
Given /^the computer is set to boot from (.+?) drive "(.+?)"$/ do |type, name|
@@ -81,7 +83,13 @@ Given /^the computer is set to boot from (.+?) drive "(.+?)"$/ do |type, name|
@vm.set_disk_boot(name, type.downcase)
end
-Given /^I plug ([[:alpha:]]+) drive "([^"]+)"$/ do |bus, name|
+Given /^I create a (\d+) ([[:alpha:]]+) disk named "([^"]+)"$/ do |size, unit, name|
+ next if @skip_steps_while_restoring_background
+ @vm.storage.create_new_disk(name, {:size => size, :unit => unit,
+ :type => "qcow2"})
+end
+
+Given /^I plug (.+) drive "([^"]+)"$/ do |bus, name|
next if @skip_steps_while_restoring_background
@vm.plug_drive(name, bus.downcase)
if @vm.is_running?
@@ -102,12 +110,14 @@ Then /^drive "([^"]+)" is detected by Tails$/ do |name|
end
Given /^the network is plugged$/ do
- next if @skip_steps_while_restoring_background
+ # We don't skip this step when restoring the background to ensure
+ # that the network state is actually the same after restoring as
+ # when the snapshot was made.
@vm.plug_network
end
Given /^the network is unplugged$/ do
- next if @skip_steps_while_restoring_background
+ # See comment in the step "the network is plugged".
@vm.unplug_network
end
@@ -115,7 +125,7 @@ Given /^I capture all network traffic$/ do
# Note: We don't want skip this particular stpe if
# @skip_steps_while_restoring_background is set since it starts
# something external to the VM state.
- @sniffer = Sniffer.new("TestSniffer", @vm.net.bridge_name)
+ @sniffer = Sniffer.new("sniffer", $vmnet)
@sniffer.capture
end
@@ -132,11 +142,11 @@ When /^I start the computer$/ do
post_vm_start_hook
end
-Given /^I start Tails from DVD(| with network unplugged) and I login$/ do |network_unplugged|
+Given /^I start Tails( from DVD)?( with network unplugged)? and I login$/ do |dvd_boot, network_unplugged|
# we don't @skip_steps_while_restoring_background as we're only running
# other steps, that are taking care of it *if* they have to
- step "the computer is set to boot from the Tails DVD"
- if network_unplugged.empty?
+ step "the computer is set to boot from the Tails DVD" if dvd_boot
+ if network_unplugged.nil?
step "the network is plugged"
else
step "the network is unplugged"
@@ -145,7 +155,7 @@ Given /^I start Tails from DVD(| with network unplugged) and I login$/ do |netwo
step "the computer boots Tails"
step "I log in to a new session"
step "Tails seems to have booted normally"
- if network_unplugged.empty?
+ if network_unplugged.nil?
step "Tor is ready"
step "all notifications have disappeared"
step "available upgrades have been checked"
@@ -199,7 +209,7 @@ end
When /^I destroy the computer$/ do
next if @skip_steps_while_restoring_background
- @vm.destroy
+ @vm.destroy_and_undefine
end
Given /^the computer (re)?boots Tails$/ do |reboot|
@@ -248,6 +258,11 @@ Given /^I enable more Tails Greeter options$/ do
@screen.wait('TailsGreeterLoginButton.png', 20)
end
+Given /^I enable the specific Tor configuration option$/ do
+ next if @skip_steps_while_restoring_background
+ @screen.click('TailsGreeterTorConf.png')
+end
+
Given /^I set sudo password "([^"]*)"$/ do |password|
@sudo_password = password
next if @skip_steps_while_restoring_background
@@ -282,15 +297,15 @@ Then /^Tails seems to have booted normally$/ do
step "GNOME has started"
end
-Given /^Tor is ready$/ do
+When /^I see the 'Tor is ready' notification$/ do
next if @skip_steps_while_restoring_background
@screen.wait("GnomeTorIsReady.png", 300)
@screen.waitVanish("GnomeTorIsReady.png", 15)
+end
- # Having seen the "Tor is ready" notification implies that Tor has
- # built a circuit, but let's check it directly to be on the safe side.
+Given /^Tor is ready$/ do
+ next if @skip_steps_while_restoring_background
step "Tor has built a circuit"
-
step "the time has synced"
end
@@ -325,10 +340,18 @@ Given /^the Tor Browser has started$/ do
@screen.wait(tor_browser_picture, 60)
end
-Given /^the Tor Browser has started and loaded the startup page$/ do
+Given /^the Tor Browser has started and loaded the (startup page|Tails roadmap)$/ do |page|
next if @skip_steps_while_restoring_background
+ case page
+ when "startup page"
+ picture = "TorBrowserStartupPage.png"
+ when "Tails roadmap"
+ picture = "TorBrowserTailsRoadmap.png"
+ else
+ raise "Unsupported page: #{page}"
+ end
step "the Tor Browser has started"
- @screen.wait("TorBrowserStartupPage.png", 120)
+ @screen.wait(picture, 120)
end
Given /^the Tor Browser has started in offline mode$/ do
@@ -397,29 +420,8 @@ end
Then /^all Internet traffic has only flowed through Tor$/ do
next if @skip_steps_while_restoring_background
- leaks = FirewallLeakCheck.new(@sniffer.pcap_file, get_tor_relays)
- if !leaks.empty?
- if !leaks.ipv4_tcp_leaks.empty?
- puts "The following IPv4 TCP non-Tor Internet hosts were contacted:"
- puts leaks.ipv4_tcp_leaks.join("\n")
- puts
- end
- if !leaks.ipv4_nontcp_leaks.empty?
- puts "The following IPv4 non-TCP Internet hosts were contacted:"
- puts leaks.ipv4_nontcp_leaks.join("\n")
- puts
- end
- if !leaks.ipv6_leaks.empty?
- puts "The following IPv6 Internet hosts were contacted:"
- puts leaks.ipv6_leaks.join("\n")
- puts
- end
- if !leaks.nonip_leaks.empty?
- puts "Some non-IP packets were sent\n"
- end
- save_pcap_file
- raise "There were network leaks!"
- end
+ leaks = FirewallLeakCheck.new(@sniffer.pcap_file, get_all_tor_nodes)
+ leaks.assert_no_leaks
end
Given /^I enter the sudo password in the gksu prompt$/ do
@@ -438,7 +440,6 @@ end
def deal_with_polkit_prompt (image, password)
@screen.wait(image, 60)
- sleep 1 # wait for weird fade-in to unblock the "Ok" button
@screen.type(password)
@screen.type(Sikuli::Key.ENTER)
@screen.waitVanish(image, 10)
@@ -463,6 +464,14 @@ Given /^process "([^"]+)" is running within (\d+) seconds$/ do |process, time|
end
end
+Given /^process "([^"]+)" has stopped running after at most (\d+) seconds$/ do |process, time|
+ next if @skip_steps_while_restoring_background
+ try_for(time.to_i, :msg => "Process '#{process}' is still running after " +
+ "waiting for #{time} seconds") do
+ not @vm.has_process?(process)
+ end
+end
+
Given /^process "([^"]+)" is not running$/ do |process|
next if @skip_steps_while_restoring_background
assert(!@vm.has_process?(process),
@@ -543,6 +552,70 @@ When /^I start the Tor Browser in offline mode$/ do
end
end
+def xul_application_info(application)
+ binary = @vm.execute_successfully(
+ '. /usr/local/lib/tails-shell-library/tor-browser.sh; ' +
+ 'echo ${TBB_INSTALL}/firefox'
+ ).stdout.chomp
+ case application
+ when "Tor Browser"
+ user = LIVE_USER
+ cmd_regex = "#{binary} .* -profile /home/#{user}/\.tor-browser/profile\.default"
+ chroot = ""
+ new_tab_button_image = "TorBrowserNewTabButton.png"
+ address_bar_image = "TorBrowserAddressBar.png"
+ when "Unsafe Browser"
+ user = "clearnet"
+ cmd_regex = "#{binary} .* -profile /home/#{user}/\.unsafe-browser/profile\.default"
+ chroot = "/var/lib/unsafe-browser/chroot"
+ new_tab_button_image = "UnsafeBrowserNewTabButton.png"
+ address_bar_image = "UnsafeBrowserAddressBar.png"
+ when "I2P Browser"
+ user = "i2pbrowser"
+ cmd_regex = "#{binary} .* -profile /home/#{user}/\.i2p-browser/profile\.default"
+ chroot = "/var/lib/i2p-browser/chroot"
+ new_tab_button_image = nil
+ address_bar_image = nil
+ when "Tor Launcher"
+ user = "tor-launcher"
+ cmd_regex = "#{binary} -app /home/#{user}/\.tor-launcher/tor-launcher-standalone/application\.ini"
+ chroot = ""
+ new_tab_button_image = nil
+ address_bar_image = nil
+ else
+ raise "Invalid browser or XUL application: #{application}"
+ end
+ return {
+ :user => user,
+ :cmd_regex => cmd_regex,
+ :chroot => chroot,
+ :new_tab_button_image => new_tab_button_image,
+ :address_bar_image => address_bar_image,
+ }
+end
+
+When /^I open a new tab in the (.*)$/ do |browser|
+ next if @skip_steps_while_restoring_background
+ info = xul_application_info(browser)
+ @screen.click(info[:new_tab_button_image])
+ @screen.wait(info[:address_bar_image], 10)
+end
+
+When /^I open the address "([^"]*)" in the (.*)$/ do |address, browser|
+ next if @skip_steps_while_restoring_background
+ step "I open a new tab in the #{browser}"
+ info = xul_application_info(browser)
+ @screen.click(info[:address_bar_image])
+ sleep 0.5
+ @screen.type(address + Sikuli::Key.ENTER)
+end
+
+Then /^the (.*) has no plugins installed$/ do |browser|
+ next if @skip_steps_while_restoring_background
+ step "I open the address \"about:plugins\" in the #{browser}"
+ step "I see \"TorBrowserNoPlugins.png\" after at most 30 seconds"
+end
+
def xul_app_shared_lib_check(pid, chroot)
expected_absent_tbb_libs = ['libnssdbm3.so']
absent_tbb_libs = []
@@ -574,31 +647,32 @@ def xul_app_shared_lib_check(pid, chroot)
"Native libs that we don't want: #{unwanted_native_libs}")
end
-Then /^(.*) uses all expected TBB shared libraries$/ do |application|
+Then /^the (.*) uses all expected TBB shared libraries$/ do |application|
next if @skip_steps_while_restoring_background
- binary = @vm.execute_successfully(
- '. /usr/local/lib/tails-shell-library/tor-browser.sh; ' +
- 'echo ${TBB_INSTALL}/firefox'
- ).stdout.chomp
- case application
- when "the Tor Browser"
- user = $live_user
- cmd_regex = "#{binary} .* -profile /home/#{user}/\.tor-browser/profile\.default"
- chroot = ""
- when "the Unsafe Browser"
- user = "clearnet"
- cmd_regex = "#{binary} .* -profile /home/#{user}/\.tor-browser/profile\.default"
- chroot = "/var/lib/unsafe-browser/chroot"
- when "Tor Launcher"
- user = "tor-launcher"
- cmd_regex = "#{binary} -app /home/#{user}/\.tor-launcher/tor-launcher-standalone/application\.ini"
- chroot = ""
- else
- raise "Invalid browser or XUL application: #{application}"
- end
- pid = @vm.execute_successfully("pgrep --uid #{user} --full --exact '#{cmd_regex}'").stdout.chomp
+ info = xul_application_info(application)
+ pid = @vm.execute_successfully("pgrep --uid #{info[:user]} --full --exact '#{info[:cmd_regex]}'").stdout.chomp
assert(/\A\d+\z/.match(pid), "It seems like #{application} is not running")
- xul_app_shared_lib_check(pid, chroot)
+ xul_app_shared_lib_check(pid, info[:chroot])
+end
+
+Then /^the (.*) chroot is torn down$/ do |browser|
+ next if @skip_steps_while_restoring_background
+ info = xul_application_info(browser)
+ try_for(30, :msg => "The #{browser} chroot '#{info[:chroot]}' was " \
+ "not removed") do
+ !@vm.execute("test -d '#{info[:chroot]}'").success?
+ end
+end
+
+Then /^the (.*) runs as the expected user$/ do |browser|
+ next if @skip_steps_while_restoring_background
+ info = xul_application_info(browser)
+ assert_vmcommand_success(@vm.execute(
+ "pgrep --full --exact '#{info[:cmd_regex]}'"),
+ "The #{browser} is not running")
+ assert_vmcommand_success(@vm.execute(
+ "pgrep --uid #{info[:user]} --full --exact '#{info[:cmd_regex]}'"),
+ "The #{browser} is not running as the #{info[:user]} user")
end
Given /^I add a wired DHCP NetworkManager connection called "([^"]+)"$/ do |con_name|
@@ -649,21 +723,42 @@ When /^I run "([^"]+)" in GNOME Terminal$/ do |command|
@screen.type(command + Sikuli::Key.ENTER)
end
-When /^the file "([^"]+)" exists$/ do |file|
+When /^the file "([^"]+)" exists(?:| after at most (\d+) seconds)$/ do |file, timeout|
+ next if @skip_steps_while_restoring_background
+ timeout = 0 if timeout.nil?
+ try_for(
+ timeout.to_i,
+ :msg => "The file #{file} does not exist after #{timeout} seconds"
+ ) {
+ @vm.file_exist?(file)
+ }
+end
+
+When /^the file "([^"]+)" does not exist$/ do |file|
next if @skip_steps_while_restoring_background
- assert(@vm.file_exist?(file))
+ assert(! (@vm.file_exist?(file)))
+end
+
+When /^the directory "([^"]+)" exists$/ do |directory|
+ next if @skip_steps_while_restoring_background
+ assert(@vm.directory_exist?(directory))
+end
+
+When /^the directory "([^"]+)" does not exist$/ do |directory|
+ next if @skip_steps_while_restoring_background
+ assert(! (@vm.directory_exist?(directory)))
end
When /^I copy "([^"]+)" to "([^"]+)" as user "([^"]+)"$/ do |source, destination, user|
next if @skip_steps_while_restoring_background
- c = @vm.execute("cp \"#{source}\" \"#{destination}\"", $live_user)
+ c = @vm.execute("cp \"#{source}\" \"#{destination}\"", LIVE_USER)
assert(c.success?, "Failed to copy file:\n#{c.stdout}\n#{c.stderr}")
end
Given /^the USB drive "([^"]+)" contains Tails with persistence configured and password "([^"]+)"$/ do |drive, password|
step "a computer"
step "I start Tails from DVD with network unplugged and I login"
- step "I create a new 4 GiB USB drive named \"#{drive}\""
+ step "I create a 4 GiB disk named \"#{drive}\""
step "I plug USB drive \"#{drive}\""
step "I \"Clone & Install\" Tails to USB drive \"#{drive}\""
step "there is no persistence partition on USB drive \"#{drive}\""
@@ -684,8 +779,12 @@ Given /^I start "([^"]+)" via the GNOME "([^"]+)" applications menu$/ do |app, s
prefix = 'Gnome'
end
@screen.wait_and_click(prefix + "ApplicationsMenu.png", 10)
- @screen.wait_and_hover(prefix + "Applications" + submenu + ".png", 20)
- @screen.wait_and_click(prefix + "Applications" + app + ".png", 20)
+ @screen.hide_cursor
+ # Wait for the menu to be displayed, by waiting for one of its last entries
+ @screen.wait(prefix + "ApplicationsTails.png", 40)
+ @screen.wait_and_hover(prefix + "Applications" + submenu + ".png", 40)
+ @screen.hide_cursor
+ @screen.wait_and_click(prefix + "Applications" + app + ".png", 40)
end
Given /^I start "([^"]+)" via the GNOME "([^"]+)"\/"([^"]+)" applications menu$/ do |app, submenu, subsubmenu|
@@ -697,7 +796,144 @@ Given /^I start "([^"]+)" via the GNOME "([^"]+)"\/"([^"]+)" applications menu$/
prefix = 'Gnome'
end
@screen.wait_and_click(prefix + "ApplicationsMenu.png", 10)
+ @screen.hide_cursor
+ # Wait for the menu to be displayed, by waiting for one of its last entries
+ @screen.wait(prefix + "ApplicationsTails.png", 40)
@screen.wait_and_hover(prefix + "Applications" + submenu + ".png", 20)
+ @screen.hide_cursor
@screen.wait_and_hover(prefix + "Applications" + subsubmenu + ".png", 20)
+ @screen.hide_cursor
@screen.wait_and_click(prefix + "Applications" + app + ".png", 20)
end
+
+When /^I type "([^"]+)"$/ do |string|
+ next if @skip_steps_while_restoring_background
+ @screen.type(string)
+end
+
+When /^I press the "([^"]+)" key$/ do |key|
+ next if @skip_steps_while_restoring_background
+ begin
+ @screen.type(eval("Sikuli::Key.#{key}"))
+ rescue RuntimeError
+ raise "unsupported key #{key}"
+ end
+end
+
+Then /^the (amnesiac|persistent) Tor Browser directory (exists|does not exist)$/ do |persistent_or_not, mode|
+ next if @skip_steps_while_restoring_background
+ case persistent_or_not
+ when "amnesiac"
+ dir = "/home/#{LIVE_USER}/Tor Browser"
+ when "persistent"
+ dir = "/home/#{LIVE_USER}/Persistent/Tor Browser"
+ end
+ step "the directory \"#{dir}\" #{mode}"
+end
+
+Then /^there is a GNOME bookmark for the (amnesiac|persistent) Tor Browser directory$/ do |persistent_or_not|
+ next if @skip_steps_while_restoring_background
+ case persistent_or_not
+ when "amnesiac"
+ bookmark_image = 'TorBrowserAmnesicFilesBookmark.png'
+ when "persistent"
+ bookmark_image = 'TorBrowserPersistentFilesBookmark.png'
+ end
+ @screen.wait_and_click('GnomePlaces.png', 10)
+ @screen.wait(bookmark_image, 40)
+ @screen.type(Sikuli::Key.ESC)
+end
+
+Then /^there is no GNOME bookmark for the persistent Tor Browser directory$/ do
+ next if @skip_steps_while_restoring_background
+ @screen.wait_and_click('GnomePlaces.png', 10)
+ @screen.wait("GnomePlacesWithoutTorBrowserPersistent.png", 40)
+ @screen.type(Sikuli::Key.ESC)
+end
+
+def pulseaudio_sink_inputs
+ pa_info = @vm.execute_successfully('pacmd info', LIVE_USER).stdout
+ sink_inputs_line = pa_info.match(/^\d+ sink input\(s\) available\.$/)[0]
+ return sink_inputs_line.match(/^\d+/)[0].to_i
+end
+
+When /^(no|\d+) application(?:s?) (?:is|are) playing audio(?:| after (\d+) seconds)$/ do |nb, wait_time|
+ next if @skip_steps_while_restoring_background
+ nb = 0 if nb == "no"
+ sleep wait_time.to_i if ! wait_time.nil?
+ assert_equal(nb.to_i, pulseaudio_sink_inputs)
+end
+
+When /^I double-click on the "Tails documentation" link on the Desktop$/ do
+ next if @skip_steps_while_restoring_background
+ @screen.wait_and_double_click("DesktopTailsDocumentationIcon.png", 10)
+end
+
+When /^I click the blocked video icon$/ do
+ next if @skip_steps_while_restoring_background
+ @screen.wait_and_click("TorBrowserBlockedVideo.png", 30)
+end
+
+When /^I accept to temporarily allow playing this video$/ do
+ next if @skip_steps_while_restoring_background
+ @screen.wait_and_click("TorBrowserOkButton.png", 10)
+end
+
+When /^I click the HTML5 play button$/ do
+ next if @skip_steps_while_restoring_background
+ @screen.wait_and_click("TorBrowserHtml5PlayButton.png", 30)
+end
+
+When /^I can save the current page as "([^"]+[.]html)" to the (default downloads|persistent Tor Browser) directory$/ do |output_file, output_dir|
+ next if @skip_steps_while_restoring_background
+ @screen.type("s", Sikuli::KeyModifier.CTRL)
+ if output_dir == "persistent Tor Browser"
+ output_dir = "/home/#{LIVE_USER}/Persistent/Tor Browser"
+ @screen.wait_and_click("GtkTorBrowserPersistentBookmark.png", 10)
+ @screen.wait("GtkTorBrowserPersistentBookmarkSelected.png", 10)
+ # The output filename (without its extension) is already selected,
+ # let's use the keyboard shortcut to focus its field
+ @screen.type("n", Sikuli::KeyModifier.ALT)
+ @screen.wait("TorBrowserSaveOutputFileSelected.png", 10)
+ else
+ output_dir = "/home/#{LIVE_USER}/Tor Browser"
+ end
+ # Only the part of the filename before the .html extension can be easily replaced
+ # so we have to remove it before typing it into the arget filename entry widget.
+ @screen.type(output_file.sub(/[.]html$/, ''))
+ @screen.type(Sikuli::Key.ENTER)
+ try_for(10, :msg => "The page was not saved to #{output_dir}/#{output_file}") {
+ @vm.file_exist?("#{output_dir}/#{output_file}")
+ }
+end
+
+When /^I can print the current page as "([^"]+[.]pdf)" to the (default downloads|persistent Tor Browser) directory$/ do |output_file, output_dir|
+ next if @skip_steps_while_restoring_background
+ if output_dir == "persistent Tor Browser"
+ output_dir = "/home/#{LIVE_USER}/Persistent/Tor Browser"
+ else
+ output_dir = "/home/#{LIVE_USER}/Tor Browser"
+ end
+ @screen.type("p", Sikuli::KeyModifier.CTRL)
+ @screen.wait("TorBrowserPrintDialog.png", 10)
+ @screen.wait_and_click("PrintToFile.png", 10)
+ # Tor Browser is not allowed to read /home/#{LIVE_USER}, and I found no way
+ # to change the default destination directory for "Print to File",
+ # so let's click through the warning
+ @screen.wait("TorBrowserCouldNotReadTheContentsOfWarning.png", 10)
+ @screen.wait_and_click("TorBrowserWarningDialogOkButton.png", 10)
+ @screen.wait_and_double_click("TorBrowserPrintOutputFile.png", 10)
+ @screen.hide_cursor
+ @screen.wait("TorBrowserPrintOutputFileSelected.png", 10)
+ # Only the file's basename is selected by double-clicking,
+ # so we type only the desired file's basename to replace it
+ @screen.type(output_dir + '/' + output_file.sub(/[.]pdf$/, '') + Sikuli::Key.ENTER)
+ try_for(30, :msg => "The page was not printed to #{output_dir}/#{output_file}") {
+ @vm.file_exist?("#{output_dir}/#{output_file}")
+ }
+end
+
+When /^I accept to import the key with Seahorse$/ do
+ next if @skip_steps_while_restoring_background
+ @screen.wait_and_click("TorBrowserOkButton.png", 10)
+end
diff --git a/features/step_definitions/encryption.rb b/features/step_definitions/encryption.rb
index 291b7a4..561b40a 100644
--- a/features/step_definitions/encryption.rb
+++ b/features/step_definitions/encryption.rb
@@ -15,9 +15,9 @@ Given /^I generate an OpenPGP key named "([^"]+)" with password "([^"]+)"$/ do |
%commit
EOF
gpg_key_recipie.split("\n").each do |line|
- @vm.execute("echo '#{line}' >> /tmp/gpg_key_recipie", $live_user)
+ @vm.execute("echo '#{line}' >> /tmp/gpg_key_recipie", LIVE_USER)
end
- c = @vm.execute("gpg --batch --gen-key < /tmp/gpg_key_recipie", $live_user)
+ c = @vm.execute("gpg --batch --gen-key < /tmp/gpg_key_recipie", LIVE_USER)
assert(c.success?, "Failed to generate OpenPGP key:\n#{c.stderr}")
end
@@ -40,31 +40,32 @@ def maybe_deal_with_pinentry
end
end
+def gedit_copy_all_text
+ @screen.click("GeditEdit.png")
+ @screen.wait_and_click("GeditSelectAll.png", 10)
+ @screen.click("GeditCopy.png")
+end
+
+def paste_into_a_new_tab
+ @screen.click("GeditNewDocument.png")
+ @screen.click("GeditPaste.png")
+end
+
def encrypt_sign_helper
- @screen.wait_and_click("GeditWindow.png", 10)
- @screen.type("a", Sikuli::KeyModifier.CTRL)
- sleep 0.5
+ gedit_copy_all_text
@screen.click("GpgAppletIconNormal.png")
- sleep 2
- @screen.type("k")
+ @screen.wait_and_click("GpgAppletSignEncrypt.png", 10)
@screen.wait_and_click("GpgAppletChooseKeyWindow.png", 30)
sleep 0.5
yield
maybe_deal_with_pinentry
- @screen.wait_and_click("GeditWindow.png", 10)
- sleep 0.5
- @screen.type("n", Sikuli::KeyModifier.CTRL)
- sleep 0.5
- @screen.type("v", Sikuli::KeyModifier.CTRL)
+ paste_into_a_new_tab
end
def decrypt_verify_helper(icon)
- @screen.wait_and_click("GeditWindow.png", 10)
- @screen.type("a", Sikuli::KeyModifier.CTRL)
- sleep 0.5
+ gedit_copy_all_text
@screen.click(icon)
- sleep 2
- @screen.type("d")
+ @screen.wait_and_click("GpgAppletDecryptVerify.png", 10)
maybe_deal_with_pinentry
@screen.wait("GpgAppletResults.png", 10)
@screen.wait("GpgAppletResultsMsg.png", 10)
@@ -87,8 +88,6 @@ When /^I sign the message using my OpenPGP key$/ do
next if @skip_steps_while_restoring_background
encrypt_sign_helper do
@screen.type(Sikuli::Key.TAB + Sikuli::Key.DOWN + Sikuli::Key.ENTER)
- @screen.wait("PinEntryPrompt.png", 10)
- @screen.type(@passphrase + Sikuli::Key.ENTER)
end
end
@@ -103,8 +102,6 @@ When /^I both encrypt and sign the message using my OpenPGP key$/ do
encrypt_sign_helper do
@screen.type(@key_name + Sikuli::Key.ENTER)
@screen.type(Sikuli::Key.TAB + Sikuli::Key.DOWN + Sikuli::Key.ENTER)
- @screen.wait("PinEntryPrompt.png", 10)
- @screen.type(@passphrase + Sikuli::Key.ENTER)
end
end
@@ -118,20 +115,10 @@ end
When /^I symmetrically encrypt the message with password "([^"]+)"$/ do |pwd|
@passphrase = pwd
next if @skip_steps_while_restoring_background
- @screen.wait_and_click("GeditWindow.png", 10)
- @screen.type("a", Sikuli::KeyModifier.CTRL)
- sleep 0.5
+ gedit_copy_all_text
@screen.click("GpgAppletIconNormal.png")
- sleep 2
- @screen.type("p")
- @screen.wait("PinEntryPrompt.png", 10)
- @screen.type(@passphrase + Sikuli::Key.ENTER)
- sleep 1
- @screen.wait("PinEntryPrompt.png", 10)
- @screen.type(@passphrase + Sikuli::Key.ENTER)
- @screen.wait_and_click("GeditWindow.png", 10)
- sleep 0.5
- @screen.type("n", Sikuli::KeyModifier.CTRL)
- sleep 0.5
- @screen.type("v", Sikuli::KeyModifier.CTRL)
+ @screen.wait_and_click("GpgAppletEncryptPassphrase.png", 10)
+ maybe_deal_with_pinentry # enter password
+ maybe_deal_with_pinentry # confirm password
+ paste_into_a_new_tab
end
diff --git a/features/step_definitions/erase_memory.rb b/features/step_definitions/erase_memory.rb
index 171f997..b4bb56b 100644
--- a/features/step_definitions/erase_memory.rb
+++ b/features/step_definitions/erase_memory.rb
@@ -17,7 +17,7 @@ Given /^the computer is an old pentium without the PAE extension$/ do
end
def which_kernel
- kernel_path = @vm.execute("/usr/local/bin/tails-get-bootinfo kernel").stdout.chomp
+ kernel_path = @vm.execute("tails-get-bootinfo kernel").stdout.chomp
return File.basename(kernel_path)
end
@@ -52,7 +52,7 @@ Given /^at least (\d+) ([[:alpha:]]+) of RAM was detected$/ do |min_ram, unit|
end
def pattern_coverage_in_guest_ram
- dump = "#{$tmp_dir}/memdump"
+ dump = "#{$config["TMP_DIR"]}/memdump"
# Workaround: when dumping the guest's memory via core_dump(), libvirt
# will create files that only root can read. We therefore pre-create
# them with more permissible permissions, which libvirt will preserve
@@ -105,7 +105,7 @@ Given /^I fill the guest's memory with a known pattern(| without verifying)$/ do
# since the others otherwise may continue re-filling the same memory
# unnecessarily.
instances = (@detected_ram_m.to_f/(2**10)).ceil
- instances.times { @vm.spawn('/usr/local/sbin/fillram; killall fillram') }
+ instances.times { @vm.spawn('fillram; killall fillram') }
# We make sure that the filling has started...
try_for(10, { :msg => "fillram didn't start" }) {
@vm.has_process?("fillram")
diff --git a/features/step_definitions/evince.rb b/features/step_definitions/evince.rb
index d9bb42c..1bb122d 100644
--- a/features/step_definitions/evince.rb
+++ b/features/step_definitions/evince.rb
@@ -7,7 +7,7 @@ Then /^I can print the current document to "([^"]+)"$/ do |output_file|
next if @skip_steps_while_restoring_background
@screen.type("p", Sikuli::KeyModifier.CTRL)
@screen.wait("EvincePrintDialog.png", 10)
- @screen.wait_and_click("EvincePrintToFile.png", 10)
+ @screen.wait_and_click("PrintToFile.png", 10)
@screen.wait_and_double_click("EvincePrintOutputFile.png", 10)
@screen.hide_cursor
@screen.wait("EvincePrintOutputFileSelected.png", 10)
diff --git a/features/step_definitions/firewall_leaks.rb b/features/step_definitions/firewall_leaks.rb
index 79ae0de..3174d0d 100644
--- a/features/step_definitions/firewall_leaks.rb
+++ b/features/step_definitions/firewall_leaks.rb
@@ -1,25 +1,25 @@
Then(/^the firewall leak detector has detected (.*?) leaks$/) do |type|
next if @skip_steps_while_restoring_background
- leaks = FirewallLeakCheck.new(@sniffer.pcap_file, get_tor_relays)
+ leaks = FirewallLeakCheck.new(@sniffer.pcap_file, get_all_tor_nodes)
case type.downcase
when 'ipv4 tcp'
if leaks.ipv4_tcp_leaks.empty?
- save_pcap_file
+ leaks.save_pcap_file
raise "Couldn't detect any IPv4 TCP leaks"
end
when 'ipv4 non-tcp'
if leaks.ipv4_nontcp_leaks.empty?
- save_pcap_file
+ leaks.save_pcap_file
raise "Couldn't detect any IPv4 non-TCP leaks"
end
when 'ipv6'
if leaks.ipv6_leaks.empty?
- save_pcap_file
+ leaks.save_pcap_file
raise "Couldn't detect any IPv6 leaks"
end
when 'non-ip'
if leaks.nonip_leaks.empty?
- save_pcap_file
+ leaks.save_pcap_file
raise "Couldn't detect any non-IP leaks"
end
else
@@ -29,7 +29,7 @@ end
Given(/^I disable Tails' firewall$/) do
next if @skip_steps_while_restoring_background
- @vm.execute("/usr/local/sbin/do_not_ever_run_me")
+ @vm.execute("do_not_ever_run_me")
iptables = @vm.execute("iptables -L -n -v").stdout.chomp.split("\n")
for line in iptables do
if !line[/Chain (INPUT|OUTPUT|FORWARD) \(policy ACCEPT/] and
@@ -42,19 +42,19 @@ end
When(/^I do a TCP DNS lookup of "(.*?)"$/) do |host|
next if @skip_steps_while_restoring_background
- lookup = @vm.execute("host -T #{host} #{$some_dns_server}", $live_user)
+ lookup = @vm.execute("host -T #{host} #{SOME_DNS_SERVER}", LIVE_USER)
assert(lookup.success?, "Failed to resolve #{host}:\n#{lookup.stdout}")
end
When(/^I do a UDP DNS lookup of "(.*?)"$/) do |host|
next if @skip_steps_while_restoring_background
- lookup = @vm.execute("host #{host} #{$some_dns_server}", $live_user)
+ lookup = @vm.execute("host #{host} #{SOME_DNS_SERVER}", LIVE_USER)
assert(lookup.success?, "Failed to resolve #{host}:\n#{lookup.stdout}")
end
When(/^I send some ICMP pings$/) do
next if @skip_steps_while_restoring_background
# We ping an IP address to avoid a DNS lookup
- ping = @vm.execute("ping -c 5 #{$some_dns_server}", $live_user)
- assert(ping.success?, "Failed to ping #{$some_dns_server}:\n#{ping.stderr}")
+ ping = @vm.execute("ping -c 5 #{SOME_DNS_SERVER}", LIVE_USER)
+ assert(ping.success?, "Failed to ping #{SOME_DNS_SERVER}:\n#{ping.stderr}")
end
diff --git a/features/step_definitions/git.rb b/features/step_definitions/git.rb
new file mode 100644
index 0000000..e991ba4
--- /dev/null
+++ b/features/step_definitions/git.rb
@@ -0,0 +1,24 @@
+Then /^the Git repository "([\S]+)" has been cloned successfully$/ do |repo|
+ next if @skip_steps_while_restoring_background
+ assert(@vm.directory_exist?("/home/#{LIVE_USER}/#{repo}/.git"))
+ assert(@vm.file_exist?("/home/#{LIVE_USER}/#{repo}/.git/config"))
+ @vm.execute_successfully("cd '/home/#{LIVE_USER}/#{repo}/' && git status", LIVE_USER)
+end
+
+Given /^I have the SSH key pair for a Git repository$/ do
+ next if @skip_steps_while_restoring_background
+ @vm.execute_successfully("install -m 0700 -d '/home/#{LIVE_USER}/.ssh/'", LIVE_USER)
+ secret_ssh_key = $config["Unsafe_SSH_private_key"]
+ public_ssh_key = $config["Unsafe_SSH_public_key"]
+ assert(!secret_ssh_key.nil? && secret_ssh_key.length > 0)
+ assert(!public_ssh_key.nil? && public_ssh_key.length > 0)
+ @vm.execute_successfully("echo '#{secret_ssh_key}' > '/home/#{LIVE_USER}/.ssh/id_rsa'", LIVE_USER)
+ @vm.execute_successfully("echo '#{public_ssh_key}' > '/home/#{LIVE_USER}/.ssh/id_rsa.pub'", LIVE_USER)
+ @vm.execute_successfully("chmod 0600 '/home/#{LIVE_USER}/.ssh/'id*", LIVE_USER)
+end
+
+Given /^I verify the SSH fingerprint for the Git repository$/ do
+ next if @skip_steps_while_restoring_background
+ @screen.wait("GitSSHFingerprint.png", 60)
+ @screen.type('yes' + Sikuli::Key.ENTER)
+end
diff --git a/features/step_definitions/i2p.rb b/features/step_definitions/i2p.rb
index 78644ae..4a20afa 100644
--- a/features/step_definitions/i2p.rb
+++ b/features/step_definitions/i2p.rb
@@ -7,7 +7,7 @@ end
Given /^the I2P router console is ready$/ do
next if @skip_steps_while_restoring_background
- try_for(60) do
+ try_for(120) do
@vm.execute('. /usr/local/lib/tails-shell-library/i2p.sh; ' +
'i2p_router_console_is_ready').success?
end
@@ -50,8 +50,10 @@ Then /^the I2P firewall rules are (enabled|disabled)$/ do |mode|
accept_rules_count = accept_rules.lines.count
if mode == 'enabled'
assert_equal(13, accept_rules_count)
+ step 'the firewall is configured to only allow the clearnet, i2psvc and debian-tor users to connect directly to the Internet over IPv4'
elsif mode == 'disabled'
assert_equal(0, accept_rules_count)
+ step 'the firewall is configured to only allow the clearnet and debian-tor users to connect directly to the Internet over IPv4'
else
raise "Unsupported mode passed: '#{mode}'"
end
diff --git a/features/step_definitions/pidgin.rb b/features/step_definitions/pidgin.rb
index 5e673e3..eb719ea 100644
--- a/features/step_definitions/pidgin.rb
+++ b/features/step_definitions/pidgin.rb
@@ -1,7 +1,7 @@
def configured_pidgin_accounts
accounts = []
xml = REXML::Document.new(@vm.file_content('$HOME/.purple/accounts.xml',
- $live_user))
+ LIVE_USER))
xml.elements.each("account/account") do |e|
account = e.elements["name"].text
account_name, network = account.split("@")
@@ -43,7 +43,7 @@ def default_chan (account)
end
def pidgin_otr_keys
- return @vm.file_content('$HOME/.purple/otr.private_key', $live_user)
+ return @vm.file_content('$HOME/.purple/otr.private_key', LIVE_USER)
end
Given /^Pidgin has the expected accounts configured with random nicknames$/ do
@@ -81,7 +81,7 @@ end
When /^I see Pidgin's account manager window$/ do
next if @skip_steps_while_restoring_background
- @screen.wait("PidginAccountWindow.png", 20)
+ @screen.wait("PidginAccountWindow.png", 40)
end
When /^I close Pidgin's account manager window$/ do
@@ -101,7 +101,7 @@ end
def focus_pidgin_buddy_list
@vm.execute_successfully(
- "xdotool search --name 'Buddy List' windowactivate --sync", $live_user
+ "xdotool search --name 'Buddy List' windowactivate --sync", LIVE_USER
)
end
@@ -192,3 +192,11 @@ When /^I close Pidgin's certificate import failure dialog$/ do
# @screen.wait_and_click('PidginCertificateManagerClose.png', 10)
@screen.waitVanish('PidginCertificateImportFailed.png', 10)
end
+
+When /^I see the Tails roadmap URL$/ do
+ @screen.wait('PidginTailsRoadmapUrl.png', 10)
+end
+
+When /^I click on the Tails roadmap URL$/ do
+ @screen.click('PidginTailsRoadmapUrl.png')
+end
diff --git a/features/step_definitions/po.rb b/features/step_definitions/po.rb
new file mode 100644
index 0000000..04fd12d
--- /dev/null
+++ b/features/step_definitions/po.rb
@@ -0,0 +1,8 @@
+Given /^I am in the Git branch being tested$/ do
+ File.exists?("#{GIT_DIR}/wiki/src/contribute/l10n_tricks/check_po.sh")
+end
+
+Given /^all the PO files should be correct$/ do
+ Dir.chdir(GIT_DIR)
+ cmd_helper('./wiki/src/contribute/l10n_tricks/check_po.sh')
+end
diff --git a/features/step_definitions/root_access_control.rb b/features/step_definitions/root_access_control.rb
index aaebb0d..026fa8e 100644
--- a/features/step_definitions/root_access_control.rb
+++ b/features/step_definitions/root_access_control.rb
@@ -1,13 +1,13 @@
Then /^I should be able to run administration commands as the live user$/ do
next if @skip_steps_while_restoring_background
- stdout = @vm.execute("echo #{@sudo_password} | sudo -S whoami", $live_user).stdout
- actual_user = stdout.sub(/^\[sudo\] password for #{$live_user}: /, "").chomp
+ stdout = @vm.execute("echo #{@sudo_password} | sudo -S whoami", LIVE_USER).stdout
+ actual_user = stdout.sub(/^\[sudo\] password for #{LIVE_USER}: /, "").chomp
assert_equal("root", actual_user, "Could not use sudo")
end
Then /^I should not be able to run administration commands as the live user with the "([^"]*)" password$/ do |password|
next if @skip_steps_while_restoring_background
- stderr = @vm.execute("echo #{password} | sudo -S whoami", $live_user).stderr
+ stderr = @vm.execute("echo #{password} | sudo -S whoami", LIVE_USER).stderr
sudo_failed = stderr.include?("The administration password is disabled") || stderr.include?("is not allowed to execute")
assert(sudo_failed, "The administration password is not disabled:" + stderr)
end
diff --git a/features/step_definitions/time_syncing.rb b/features/step_definitions/time_syncing.rb
index 161a416..d9881aa 100644
--- a/features/step_definitions/time_syncing.rb
+++ b/features/step_definitions/time_syncing.rb
@@ -1,11 +1,35 @@
+# In some steps below we allow some slack when verifying that the date
+# was set appropriately because it may take time to send the `date`
+# command over the remote shell and get the answer back, parsing and
+# post-processing of the result, etc.
+def max_time_drift
+ 5
+end
+
When /^I set the system time to "([^"]+)"$/ do |time|
next if @skip_steps_while_restoring_background
- @vm.execute("date -s '#{time}'")
+ @vm.execute_successfully("date -s '#{time}'")
+ new_time = DateTime.parse(@vm.execute_successfully("date").stdout).to_time
+ expected_time_lower_bound = DateTime.parse(time).to_time
+ expected_time_upper_bound = expected_time_lower_bound + max_time_drift
+ assert(expected_time_lower_bound <= new_time &&
+ new_time <= expected_time_upper_bound,
+ "The guest's time was supposed to be set to " \
+ "'#{expected_time_lower_bound}' but is '#{new_time}'")
end
When /^I bump the system time with "([^"]+)"$/ do |timediff|
next if @skip_steps_while_restoring_background
- @vm.execute("date -s 'now #{timediff}'")
+ old_time = DateTime.parse(@vm.execute_successfully("date").stdout).to_time
+ @vm.execute_successfully("date -s 'now #{timediff}'")
+ new_time = DateTime.parse(@vm.execute_successfully("date").stdout).to_time
+ expected_time_lower_bound = DateTime.parse(
+ cmd_helper("date -d '#{old_time} #{timediff}'")).to_time
+ expected_time_upper_bound = expected_time_lower_bound + max_time_drift
+ assert(expected_time_lower_bound <= new_time &&
+ new_time <= expected_time_upper_bound,
+ "The guest's time was supposed to be bumped to " \
+ "'#{expected_time_lower_bound}' but is '#{new_time}'")
end
Then /^Tails clock is less than (\d+) minutes incorrect$/ do |max_diff_mins|
diff --git a/features/step_definitions/tor.rb b/features/step_definitions/tor.rb
new file mode 100644
index 0000000..ff60540
--- /dev/null
+++ b/features/step_definitions/tor.rb
@@ -0,0 +1,375 @@
+def iptables_parse(iptables_output)
+ chains = Hash.new
+ cur_chain = nil
+ cur_chain_policy = nil
+ parser_state = :expecting_chain_def
+
+ iptables_output.split("\n").each do |line|
+ line.strip!
+ if /^Chain /.match(line)
+ assert_equal(:expecting_chain_def, parser_state)
+ m = /^Chain (.+) \(policy (.+) \d+ packets, \d+ bytes\)$/.match(line)
+ if m.nil?
+ m = /^Chain (.+) \(\d+ references\)$/.match(line)
+ end
+ assert_not_nil m
+ _, cur_chain, cur_chain_policy = m.to_a
+ chains[cur_chain] = {
+ "policy" => cur_chain_policy,
+ "rules" => Array.new
+ }
+ parser_state = :expecting_col_descs
+ elsif /^pkts\s/.match(line)
+ assert_equal(:expecting_col_descs, parser_state)
+ assert_equal(["pkts", "bytes", "target", "prot", "opt",
+ "in", "out", "source", "destination"],
+ line.split(/\s+/))
+ parser_state = :expecting_rule_or_empty
+ elsif line.empty?
+ assert_equal(:expecting_rule_or_empty, parser_state)
+ cur_chain = nil
+ parser_state = :expecting_chain_def
+ else
+ assert_equal(:expecting_rule_or_empty, parser_state)
+ pkts, _, target, prot, opt, in_iface, out_iface, source, destination, extra =
+ line.split(/\s+/, 10)
+ [pkts, target, prot, opt, in_iface, out_iface, source, destination].each do |var|
+ assert_not_empty(var)
+ assert_not_nil(var)
+ end
+ chains[cur_chain]["rules"] << {
+ "rule" => line,
+ "pkts" => pkts.to_i,
+ "target" => target,
+ "protocol" => prot,
+ "opt" => opt,
+ "in_iface" => in_iface,
+ "out_iface" => out_iface,
+ "source" => source,
+ "destination" => destination,
+ "extra" => extra
+ }
+ end
+ end
+ assert_equal(:expecting_rule_or_empty, parser_state)
+ return chains
+end
+
+Then /^the firewall's policy is to (.+) all IPv4 traffic$/ do |expected_policy|
+ next if @skip_steps_while_restoring_background
+ expected_policy.upcase!
+ iptables_output = @vm.execute_successfully("iptables -L -n -v").stdout
+ chains = iptables_parse(iptables_output)
+ ["INPUT", "FORWARD", "OUTPUT"].each do |chain_name|
+ policy = chains[chain_name]["policy"]
+ assert_equal(expected_policy, policy,
+ "Chain #{chain_name} has unexpected policy #{policy}")
+ end
+end
+
+Then /^the firewall is configured to only allow the (.+) users? to connect directly to the Internet over IPv4$/ do |users_str|
+ next if @skip_steps_while_restoring_background
+ users = users_str.split(/, | and /)
+ expected_uids = Set.new
+ users.each do |user|
+ expected_uids << @vm.execute_successfully("id -u #{user}").stdout.to_i
+ end
+ iptables_output = @vm.execute_successfully("iptables -L -n -v").stdout
+ chains = iptables_parse(iptables_output)
+ allowed_output = chains["OUTPUT"]["rules"].find_all do |rule|
+ !(["DROP", "REJECT", "LOG"].include? rule["target"]) &&
+ rule["out_iface"] != "lo"
+ end
+ uids = Set.new
+ allowed_output.each do |rule|
+ case rule["target"]
+ when "ACCEPT"
+ expected_destination = "0.0.0.0/0"
+ assert_equal(expected_destination, rule["destination"],
+ "The following rule has an unexpected destination:\n" +
+ rule["rule"])
+ next if rule["extra"] == "state RELATED,ESTABLISHED"
+ m = /owner UID match (\d+)/.match(rule["extra"])
+ assert_not_nil(m)
+ uid = m[1].to_i
+ uids << uid
+ assert(expected_uids.include?(uid),
+ "The following rule allows uid #{uid} to access the network, " \
+ "but we only expect uids #{expected_uids} (#{users_str}) to " \
+ "have such access:\n#{rule["rule"]}")
+ when "lan"
+ lan_subnets = ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
+ assert(lan_subnets.include?(rule["destination"]),
+ "The following lan-targeted rule's destination is " \
+ "#{rule["destination"]} which may not be a private subnet:\n" +
+ rule["rule"])
+ else
+ raise "Unexpected iptables OUTPUT chain rule:\n#{rule["rule"]}"
+ end
+ end
+ uids_not_found = expected_uids - uids
+ assert(uids_not_found.empty?,
+ "Couldn't find rules allowing uids #{uids_not_found.to_a.to_s} " \
+ "access to the network")
+end
+
+Then /^the firewall's NAT rules only redirect traffic for Tor's TransPort and DNSPort$/ do
+ next if @skip_steps_while_restoring_background
+ tor_onion_addr_space = "127.192.0.0/10"
+ iptables_nat_output = @vm.execute_successfully("iptables -t nat -L -n -v").stdout
+ chains = iptables_parse(iptables_nat_output)
+ chains.each_pair do |name, chain|
+ rules = chain["rules"]
+ if name == "OUTPUT"
+ good_rules = rules.find_all do |rule|
+ rule["target"] == "REDIRECT" &&
+ (
+ (
+ rule["destination"] == tor_onion_addr_space &&
+ rule["extra"] == "redir ports 9040"
+ ) ||
+ rule["extra"] == "udp dpt:53 redir ports 5353"
+ )
+ end
+ assert_equal(rules, good_rules,
+ "The NAT table's OUTPUT chain contains some unexptected " \
+ "rules:\n" +
+ ((rules - good_rules).map { |r| r["rule"] }).join("\n"))
+ else
+ assert(rules.empty?,
+ "The NAT table contains unexpected rules for the #{name} " \
+ "chain:\n" + (rules.map { |r| r["rule"] }).join("\n"))
+ end
+ end
+end
+
+Then /^the firewall is configured to block all IPv6 traffic$/ do
+ next if @skip_steps_while_restoring_background
+ expected_policy = "DROP"
+ ip6tables_output = @vm.execute_successfully("ip6tables -L -n -v").stdout
+ chains = iptables_parse(ip6tables_output)
+ chains.each_pair do |name, chain|
+ policy = chain["policy"]
+ assert_equal(expected_policy, policy,
+ "The IPv6 #{name} chain has policy #{policy} but we " \
+ "expected #{expected_policy}")
+ rules = chain["rules"]
+ bad_rules = rules.find_all do |rule|
+ !["DROP", "REJECT", "LOG"].include?(rule["target"])
+ end
+ assert(bad_rules.empty?,
+ "The IPv6 table's #{name} chain contains some unexptected rules:\n" +
+ (bad_rules.map { |r| r["rule"] }).join("\n"))
+ end
+end
+
+def firewall_has_dropped_packet_to?(proto, host, port)
+ regex = "Dropped outbound packet: .* DST=#{host} .* PROTO=#{proto} "
+ regex += ".* DPT=#{port} " if port
+ @vm.execute("grep -q '#{regex}' /var/log/syslog").success?
+end
+
+When /^I open an untorified (TCP|UDP|ICMP) connections to (\S*)(?: on port (\d+))? that is expected to fail$/ do |proto, host, port|
+ next if @skip_steps_while_restoring_background
+ assert(!firewall_has_dropped_packet_to?(proto, host, port),
+ "A #{proto} packet to #{host}" +
+ (port.nil? ? "" : ":#{port}") +
+ " has already been dropped by the firewall")
+ @conn_proto = proto
+ @conn_host = host
+ @conn_port = port
+ case proto
+ when "TCP"
+ assert_not_nil(port)
+ cmd = "echo | netcat #{host} #{port}"
+ when "UDP"
+ assert_not_nil(port)
+ cmd = "echo | netcat -u #{host} #{port}"
+ when "ICMP"
+ cmd = "ping -c 5 #{host}"
+ end
+ @conn_res = @vm.execute(cmd, LIVE_USER)
+end
+
+Then /^the untorified connection fails$/ do
+ next if @skip_steps_while_restoring_background
+ case @conn_proto
+ when "TCP"
+ expected_in_stderr = "Connection refused"
+ conn_failed = !@conn_res.success? &&
+ @conn_res.stderr.chomp.end_with?(expected_in_stderr)
+ when "UDP", "ICMP"
+ conn_failed = !@conn_res.success?
+ end
+ assert(conn_failed,
+ "The untorified #{@conn_proto} connection didn't fail as expected:\n" +
+ @conn_res.to_s)
+end
+
+Then /^the untorified connection is logged as dropped by the firewall$/ do
+ next if @skip_steps_while_restoring_background
+ assert(firewall_has_dropped_packet_to?(@conn_proto, @conn_host, @conn_port),
+ "No #{@conn_proto} packet to #{@conn_host}" +
+ (@conn_port.nil? ? "" : ":#{@conn_port}") +
+ " was dropped by the firewall")
+end
+
+When /^the system DNS is(?: still)? using the local DNS resolver$/ do
+ next if @skip_steps_while_restoring_background
+ resolvconf = @vm.file_content("/etc/resolv.conf")
+ bad_lines = resolvconf.split("\n").find_all do |line|
+ !line.start_with?("#") && !/^nameserver\s+127\.0\.0\.1$/.match(line)
+ end
+ assert_empty(bad_lines,
+ "The following bad lines were found in /etc/resolv.conf:\n" +
+ bad_lines.join("\n"))
+end
+
+def stream_isolation_info(application)
+ case application
+ when "htpdate"
+ {
+ :grep_monitor_expr => '/curl\>',
+ :socksport => 9062
+ }
+ when "tails-security-check", "tails-upgrade-frontend-wrapper"
+ # We only grep connections with ESTABLISHED state since `perl`
+ # is also used by monkeysphere's validation agent, which LISTENs
+ {
+ :grep_monitor_expr => '\<ESTABLISHED\>.\+/perl\>',
+ :socksport => 9062
+ }
+ when "Tor Browser"
+ {
+ :grep_monitor_expr => '/firefox\>',
+ :socksport => 9150
+ }
+ when "Gobby"
+ {
+ :grep_monitor_expr => '/gobby\>',
+ :socksport => 9050
+ }
+ when "SSH"
+ {
+ :grep_monitor_expr => '/\(connect-proxy\|ssh\)\>',
+ :socksport => 9050
+ }
+ when "whois"
+ {
+ :grep_monitor_expr => '/whois\>',
+ :socksport => 9050
+ }
+ else
+ raise "Unknown application '#{application}' for the stream isolation tests"
+ end
+end
+
+When /^I monitor the network connections of (.*)$/ do |application|
+ next if @skip_steps_while_restoring_background
+ @process_monitor_log = "/tmp/netstat.log"
+ info = stream_isolation_info(application)
+ @vm.spawn("while true; do " +
+ " netstat -taupen | grep \"#{info[:grep_monitor_expr]}\"; " +
+ " sleep 0.1; " +
+ "done > #{@process_monitor_log}")
+end
+
+Then /^I see that (.+) is properly stream isolated$/ do |application|
+ next if @skip_steps_while_restoring_background
+ expected_port = stream_isolation_info(application)[:socksport]
+ assert_not_nil(@process_monitor_log)
+ log_lines = @vm.file_content(@process_monitor_log).split("\n")
+ assert(log_lines.size > 0,
+ "Couldn't see any connection made by #{application} so " \
+ "something is wrong")
+ log_lines.each do |line|
+ addr_port = line.split(/\s+/)[4]
+ assert_equal("127.0.0.1:#{expected_port}", addr_port,
+ "#{application} should use SocksPort #{expected_port} but " \
+ "was seen connecting to #{addr_port}")
+ end
+end
+
+And /^I re-run tails-security-check$/ do
+ next if @skip_steps_while_restoring_background
+ @vm.execute_successfully("tails-security-check", LIVE_USER)
+end
+
+And /^I re-run htpdate$/ do
+ next if @skip_steps_while_restoring_background
+ @vm.execute_successfully("service htpdate stop && " \
+ "rm -f /var/run/htpdate/* && " \
+ "service htpdate start")
+ step "the time has synced"
+end
+
+And /^I re-run tails-upgrade-frontend-wrapper$/ do
+ next if @skip_steps_while_restoring_background
+ @vm.execute_successfully("tails-upgrade-frontend-wrapper", LIVE_USER)
+end
+
+When /^I connect Gobby to "([^"]+)"$/ do |host|
+ next if @skip_steps_while_restoring_background
+ @screen.wait("GobbyWindow.png", 30)
+ @screen.wait("GobbyWelcomePrompt.png", 10)
+ @screen.click("GnomeCloseButton.png")
+ @screen.wait("GobbyWindow.png", 10)
+ @screen.type("t", Sikuli::KeyModifier.CTRL)
+ @screen.wait("GobbyConnectPrompt.png", 10)
+ @screen.type(host + Sikuli::Key.ENTER)
+ @screen.wait("GobbyConnectionComplete.png", 60)
+end
+
+When /^the Tor Launcher autostarts$/ do
+ next if @skip_steps_while_restoring_background
+ @screen.wait('TorLauncherWindow.png', 30)
+end
+
+When /^I configure some (\w+) pluggable transports in Tor Launcher$/ do |bridge_type|
+ next if @skip_steps_while_restoring_background
+ bridge_type.downcase!
+ bridge_type.capitalize!
+ begin
+ @bridges = $config["Tor"]["Transports"][bridge_type]
+ assert_not_nil(@bridges)
+ assert(!@bridges.empty?)
+ rescue NoMethodError, Test::Unit::AssertionFailedError
+ raise(
+<<EOF
+It seems no '#{bridge_type}' pluggable transports are defined in your local configuration file (#{LOCAL_CONFIG_FILE}). See wiki/src/contribute/release_process/test/usage.mdwn for the format.
+EOF
+)
+ end
+ @bridge_hosts = []
+ for bridge in @bridges do
+ @bridge_hosts << bridge["ipv4_address"]
+ end
+
+ @screen.wait_and_click('TorLauncherConfigureButton.png', 10)
+ @screen.wait_and_click('TorLauncherNextButton.png', 10)
+ @screen.hide_cursor
+ @screen.wait_and_click('TorLauncherNextButton.png', 10)
+ @screen.wait('TorLauncherBridgePrompt.png', 10)
+ @screen.wait_and_click('TorLauncherYesRadioOption.png', 10)
+ @screen.wait_and_click('TorLauncherNextButton.png', 10)
+ @screen.wait_and_click('TorLauncherBridgeList.png', 10)
+ for bridge in @bridges do
+ bridge_line = bridge_type.downcase + " " +
+ bridge["ipv4_address"] + ":" +
+ bridge["ipv4_port"].to_s
+ bridge_line += " " + bridge["fingerprint"].to_s if bridge["fingerprint"]
+ bridge_line += " " + bridge["extra"].to_s if bridge["extra"]
+ @screen.type(bridge_line + Sikuli::Key.ENTER)
+ end
+ @screen.wait_and_click('TorLauncherFinishButton.png', 10)
+ @screen.wait('TorLauncherConnectingWindow.png', 10)
+ @screen.waitVanish('TorLauncherConnectingWindow.png', 120)
+end
+
+When /^all Internet traffic has only flowed through the configured pluggable transports$/ do
+ next if @skip_steps_while_restoring_background
+ assert_not_nil(@bridge_hosts, "No bridges has been configured via the " +
+ "'I configure some ... bridges in Tor Launcher' step")
+ leaks = FirewallLeakCheck.new(@sniffer.pcap_file, @bridge_hosts)
+ leaks.assert_no_leaks
+end
diff --git a/features/step_definitions/torified_browsing.rb b/features/step_definitions/torified_browsing.rb
deleted file mode 100644
index 770fda5..0000000
--- a/features/step_definitions/torified_browsing.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-When /^I open a new tab in the Tor Browser$/ do
- next if @skip_steps_while_restoring_background
- @screen.click("TorBrowserNewTabButton.png")
-end
-
-When /^I open the address "([^"]*)" in the Tor Browser$/ do |address|
- next if @skip_steps_while_restoring_background
- step "I open a new tab in the Tor Browser"
- @screen.click("TorBrowserAddressBar.png")
- sleep 0.5
- @screen.type(address + Sikuli::Key.ENTER)
-end
diff --git a/features/step_definitions/torified_gnupg.rb b/features/step_definitions/torified_gnupg.rb
index a993f9a..543e2e4 100644
--- a/features/step_definitions/torified_gnupg.rb
+++ b/features/step_definitions/torified_gnupg.rb
@@ -1,6 +1,6 @@
When /^the "([^"]*)" OpenPGP key is not in the live user's public keyring$/ do |keyid|
next if @skip_steps_while_restoring_background
- assert(!@vm.execute("gpg --batch --list-keys '#{keyid}'", $live_user).success?,
+ assert(!@vm.execute("gpg --batch --list-keys '#{keyid}'", LIVE_USER).success?,
"The '#{keyid}' key is in the live user's public keyring.")
end
@@ -8,7 +8,7 @@ When /^I fetch the "([^"]*)" OpenPGP key using the GnuPG CLI$/ do |keyid|
next if @skip_steps_while_restoring_background
@gnupg_recv_key_res = @vm.execute(
"gpg --batch --recv-key '#{keyid}'",
- $live_user)
+ LIVE_USER)
end
When /^the GnuPG fetch is successful$/ do
@@ -19,14 +19,14 @@ end
When /^GnuPG uses the configured keyserver$/ do
next if @skip_steps_while_restoring_background
- assert(@gnupg_recv_key_res.stderr[$configured_keyserver_hostname],
- "GnuPG's stderr did not mention keyserver #{$configured_keyserver_hostname}")
+ assert(@gnupg_recv_key_res.stderr[CONFIGURED_KEYSERVER_HOSTNAME],
+ "GnuPG's stderr did not mention keyserver #{CONFIGURED_KEYSERVER_HOSTNAME}")
end
When /^the "([^"]*)" key is in the live user's public keyring after at most (\d+) seconds$/ do |keyid, delay|
next if @skip_steps_while_restoring_background
try_for(delay.to_f, :msg => "The '#{keyid}' key is not in the live user's public keyring") {
- @vm.execute("gpg --batch --list-keys '#{keyid}'", $live_user).success?
+ @vm.execute("gpg --batch --list-keys '#{keyid}'", LIVE_USER).success?
}
end
diff --git a/features/step_definitions/torified_misc.rb b/features/step_definitions/torified_misc.rb
new file mode 100644
index 0000000..610234f
--- /dev/null
+++ b/features/step_definitions/torified_misc.rb
@@ -0,0 +1,35 @@
+When /^I query the whois directory service for "([^"]+)"$/ do |domain|
+ next if @skip_steps_while_restoring_background
+ @vm_execute_res = @vm.execute(
+ "whois '#{domain}'",
+ LIVE_USER)
+end
+
+When /^I wget "([^"]+)" to stdout(?:| with the '([^']+)' options)$/ do |url, options|
+ next if @skip_steps_while_restoring_background
+ arguments = "-O - '#{url}'"
+ arguments = "#{options} #{arguments}" if options
+ @vm_execute_res = @vm.execute(
+ "wget #{arguments}",
+ LIVE_USER)
+end
+
+Then /^the (wget|whois) command is successful$/ do |command|
+ next if @skip_steps_while_restoring_background
+ assert(
+ @vm_execute_res.success?,
+ "#{command} failed:\n" +
+ "#{@vm_execute_res.stdout}\n" +
+ "#{@vm_execute_res.stderr}"
+ )
+end
+
+Then /^the (wget|whois) standard output contains "([^"]+)"$/ do |command, text|
+ next if @skip_steps_while_restoring_background
+ assert(
+ @vm_execute_res.stdout[text],
+ "The #{command} standard output does not contain #{text}:\n" +
+ "#{@vm_execute_res.stdout}\n" +
+ "#{@vm_execute_res.stderr}"
+ )
+end
diff --git a/features/step_definitions/totem.rb b/features/step_definitions/totem.rb
index f8743d7..717ce27 100644
--- a/features/step_definitions/totem.rb
+++ b/features/step_definitions/totem.rb
@@ -7,17 +7,17 @@ Given /^I create sample videos$/ do
fatal_system("ffmpeg -loop 1 -t 30 -f image2 " +
"-i 'features/images/TailsBootSplash.png' " +
"-an -vcodec libx264 -y " +
- "'#{$misc_files_dir}/video.mp4' >/dev/null 2>&1")
+ "'#{MISC_FILES_DIR}/video.mp4' >/dev/null 2>&1")
end
Given /^I setup a filesystem share containing sample videos$/ do
next if @skip_steps_while_restoring_background
- @vm.add_share($misc_files_dir, shared_video_dir_on_guest)
+ @vm.add_share(MISC_FILES_DIR, shared_video_dir_on_guest)
end
Given /^I copy the sample videos to "([^"]+)" as user "([^"]+)"$/ do |destination, user|
next if @skip_steps_while_restoring_background
- for video_on_host in Dir.glob("#{$misc_files_dir}/*.mp4") do
+ for video_on_host in Dir.glob("#{MISC_FILES_DIR}/*.mp4") do
video_name = File.basename(video_on_host)
src_on_guest = "#{shared_video_dir_on_guest}/#{video_name}"
dst_on_guest = "#{destination}/#{video_name}"
diff --git a/features/step_definitions/unsafe_browser.rb b/features/step_definitions/unsafe_browser.rb
index a359ad7..375fd2f 100644
--- a/features/step_definitions/unsafe_browser.rb
+++ b/features/step_definitions/unsafe_browser.rb
@@ -1,7 +1,13 @@
When /^I see and accept the Unsafe Browser start verification$/ do
next if @skip_steps_while_restoring_background
@screen.wait("UnsafeBrowserStartVerification.png", 30)
- @screen.type("l", Sikuli::KeyModifier.ALT)
+ @screen.type(Sikuli::Key.ESC)
+end
+
+Then /^I start the Unsafe Browser in the "([^"]+)" locale$/ do |loc|
+ next if @skip_steps_while_restoring_background
+ step "I run \"LANG=#{loc}.UTF-8 LC_ALL=#{loc}.UTF-8 sudo unsafe-browser\" in GNOME Terminal"
+ step "I see and accept the Unsafe Browser start verification"
end
Then /^I see the Unsafe Browser start notification and wait for it to close$/ do
@@ -15,6 +21,61 @@ Then /^the Unsafe Browser has started$/ do
@screen.wait("UnsafeBrowserHomepage.png", 360)
end
+Then /^the Unsafe Browser has no add-ons installed$/ do
+ next if @skip_steps_while_restoring_background
+ step "I open the address \"about:addons\" in the Unsafe Browser"
+ step "I see \"UnsafeBrowserNoAddons.png\" after at most 30 seconds"
+end
+
+Then /^the Unsafe Browser has only Firefox's default bookmarks configured$/ do
+ next if @skip_steps_while_restoring_background
+ info = xul_application_info("Unsafe Browser")
+ # "Show all bookmarks"
+ @screen.type("o", Sikuli::KeyModifier.SHIFT + Sikuli::KeyModifier.CTRL)
+ @screen.wait_and_click("UnsafeBrowserExportBookmarksButton.png", 20)
+ @screen.wait_and_click("UnsafeBrowserExportBookmarksMenuEntry.png", 20)
+ @screen.wait("UnsafeBrowserExportBookmarksSavePrompt.png", 20)
+ path = "/home/#{info[:user]}/bookmarks"
+ @screen.type(path + Sikuli::Key.ENTER)
+ chroot_path = "#{info[:chroot]}/#{path}.json"
+ try_for(10) { @vm.file_exist?(chroot_path) }
+ dump = JSON.load(@vm.file_content(chroot_path))
+
+ def check_bookmarks_helper(a)
+ mozilla_uris_counter = 0
+ places_uris_counter = 0
+ a.each do |h|
+ h.each_pair do |k, v|
+ if k == "children"
+ m, p = check_bookmarks_helper(v)
+ mozilla_uris_counter += m
+ places_uris_counter += p
+ elsif k == "uri"
+ uri = v
+ if uri.match("^https://www\.mozilla\.org/")
+ mozilla_uris_counter += 1
+ elsif uri.match("^place:(sort|folder|type)=")
+ places_uris_counter += 1
+ else
+ raise "Unexpected Unsafe Browser bookmark for '#{uri}'"
+ end
+ end
+ end
+ end
+ return [mozilla_uris_counter, places_uris_counter]
+ end
+
+ mozilla_uris_counter, places_uris_counter =
+ check_bookmarks_helper(dump["children"])
+ assert_equal(5, mozilla_uris_counter,
+ "Unexpected number (#{mozilla_uris_counter}) of mozilla " \
+ "bookmarks")
+ assert_equal(3, places_uris_counter,
+ "Unexpected number (#{places_uris_counter}) of places " \
+ "bookmarks")
+ @screen.type(Sikuli::Key.F4, Sikuli::KeyModifier.ALT)
+end
+
Then /^the Unsafe Browser has a red theme$/ do
next if @skip_steps_while_restoring_background
@screen.wait("UnsafeBrowserRedTheme.png", 10)
@@ -59,20 +120,6 @@ Then /^I can start the Unsafe Browser again$/ do
step "I start the Unsafe Browser"
end
-When /^I open a new tab in the Unsafe Browser$/ do
- next if @skip_steps_while_restoring_background
- @screen.wait_and_click("UnsafeBrowserWindow.png", 10)
- @screen.type("t", Sikuli::KeyModifier.CTRL)
-end
-
-When /^I open the address "([^"]*)" in the Unsafe Browser$/ do |address|
- next if @skip_steps_while_restoring_background
- step "I open a new tab in the Unsafe Browser"
- @screen.type("l", Sikuli::KeyModifier.CTRL)
- sleep 0.5
- @screen.type(address + Sikuli::Key.ENTER)
-end
-
Then /^I cannot configure the Unsafe Browser to use any local proxies$/ do
next if @skip_steps_while_restoring_background
@screen.wait_and_click("UnsafeBrowserWindow.png", 10)
@@ -88,18 +135,13 @@ Then /^I cannot configure the Unsafe Browser to use any local proxies$/ do
# @screen.waitVanish('UnsafeBrowserPreferences.png', 10)
sleep 0.5
- http_proxy = 'x' # Alt+x is the shortcut to select http proxy
socks_proxy = 'c' # Alt+c for socks proxy
no_proxy = 'y' # Alt+y for no proxy
- # Note: the loop below depends on that http_proxy is done after any
- # other proxy types since it will set "Use this proxy server for all
- # protocols", which will make the other proxy types unselectable.
proxies = [[socks_proxy, 9050],
[socks_proxy, 9061],
[socks_proxy, 9062],
[socks_proxy, 9150],
- [http_proxy, 8118],
[no_proxy, 0]]
proxies.each do |proxy|
@@ -119,8 +161,6 @@ Then /^I cannot configure the Unsafe Browser to use any local proxies$/ do
# Configure the proxy
@screen.type(proxy_type, Sikuli::KeyModifier.ALT) # Select correct proxy type
@screen.type("127.0.0.1" + Sikuli::Key.TAB + "#{proxy_port}") if proxy_type != no_proxy
- # For http proxy we set "Use this proxy server for all protocols"
- @screen.type("s", Sikuli::KeyModifier.ALT) if proxy_type == http_proxy
# Close settings
@screen.type(Sikuli::Key.ENTER)
@@ -139,3 +179,56 @@ Then /^I cannot configure the Unsafe Browser to use any local proxies$/ do
end
end
end
+
+Then /^the Unsafe Browser has no proxy configured$/ do
+ next if @skip_steps_while_restoring_background
+ @screen.click('UnsafeBrowserMenuButton.png')
+ @screen.wait_and_click('UnsafeBrowserPreferencesButton.png', 10)
+ @screen.wait('UnsafeBrowserPreferencesWindow.png', 10)
+ @screen.wait_and_click('UnsafeBrowserAdvancedSettings.png', 10)
+ @screen.wait_and_click('UnsafeBrowserNetworkTab.png', 10)
+ @screen.type("e", Sikuli::KeyModifier.ALT)
+ @screen.wait('UnsafeBrowserProxySettings.png', 10)
+ @screen.wait('UnsafeBrowserNoProxySelected.png', 10)
+ @screen.type(Sikuli::Key.F4, Sikuli::KeyModifier.ALT)
+ @screen.type(Sikuli::Key.F4, Sikuli::KeyModifier.ALT)
+end
+
+Then /^the Unsafe Browser complains that no DNS server is configured$/ do
+ next if @skip_steps_while_restoring_background
+ @screen.wait("UnsafeBrowserDNSError.png", 30)
+end
+
+Then /^I configure the Unsafe Browser to check for updates more frequently$/ do
+ next if @skip_steps_while_restoring_background
+ prefs = '/usr/share/tails/unsafe-browser/prefs.js'
+ @vm.file_append(prefs, 'pref("app.update.idletime", 1);')
+ @vm.file_append(prefs, 'pref("app.update.promptWaitTime", 1);')
+ @vm.file_append(prefs, 'pref("app.update.interval", 5);')
+end
+
+But /^checking for updates is disabled in the Unsafe Browser's configuration$/ do
+ next if @skip_steps_while_restoring_background
+ prefs = '/usr/share/tails/unsafe-browser/prefs.js'
+ assert(@vm.file_content(prefs).include?('pref("app.update.enabled", false)'))
+end
+
+Then /^the clearnet user has (|not )sent packets out to the Internet$/ do |sent|
+ next if @skip_steps_while_restoring_background
+ pkts = 0
+ uid = @vm.execute_successfully("id -u clearnet").stdout.chomp.to_i
+ iptables_output = @vm.execute_successfully("iptables -vnL").stdout.chomp
+ output_chain = iptables_parse(iptables_output)["OUTPUT"]
+ output_chain["rules"].each do |rule|
+ if /owner UID match \b#{uid}\b/.match(rule["extra"])
+ pkts += rule["pkts"]
+ end
+ end
+
+ case sent
+ when ''
+ assert(pkts > 0, "Packets have not gone out to the internet.")
+ when 'not'
+ assert_equal(pkts, 0, "Packets have gone out to the internet.")
+ end
+end
diff --git a/features/step_definitions/untrusted_partitions.rb b/features/step_definitions/untrusted_partitions.rb
index de2e0a7..6965143 100644
--- a/features/step_definitions/untrusted_partitions.rb
+++ b/features/step_definitions/untrusted_partitions.rb
@@ -1,29 +1,53 @@
-Given /^I create a (\d+) ([[:alpha:]]+) disk named "([^"]+)"$/ do |size, unit, name|
+Given /^I create an? ([[:alnum:]]+) swap partition on disk "([^"]+)"$/ do |parttype, name|
next if @skip_steps_while_restoring_background
- @vm.storage.create_new_disk(name, {:size => size, :unit => unit,
- :type => "raw"})
+ @vm.storage.disk_mkswap(name, parttype)
end
-Given /^I create a ([[:alpha:]]+) label on disk "([^"]+)"$/ do |type, name|
+Then /^an? "([^"]+)" partition was detected by Tails on drive "([^"]+)"$/ do |type, name|
next if @skip_steps_while_restoring_background
- @vm.storage.disk_mklabel(name, type)
+ part_info = @vm.execute_successfully(
+ "parted -s '#{@vm.disk_dev(name)}' print 1").stdout.strip
+ assert(part_info.match("^File System:\s*#{Regexp.escape(type)}$"),
+ "No #{type} partition was detected by Tails on disk '#{name}'")
end
-Given /^I create a ([[:alnum:]]+) filesystem on disk "([^"]+)"$/ do |type, name|
+Then /^Tails has no disk swap enabled$/ do
next if @skip_steps_while_restoring_background
- @vm.storage.disk_mkpartfs(name, type)
+ # Skip first line which contain column headers
+ swap_info = @vm.execute_successfully("tail -n+2 /proc/swaps").stdout
+ assert(swap_info.empty?,
+ "Disk swapping is enabled according to /proc/swaps:\n" + swap_info)
+ mem_info = @vm.execute_successfully("grep '^Swap' /proc/meminfo").stdout
+ assert(mem_info.match(/^SwapTotal:\s+0 kB$/),
+ "Disk swapping is enabled according to /proc/meminfo:\n" +
+ mem_info)
end
-Given /^I cat an ISO hybrid of the Tails image to disk "([^"]+)"$/ do |name|
+Given /^I create an? ([[:alnum:]]+) partition( labeled "([^"]+)")? with an? ([[:alnum:]]+) filesystem( encrypted with password "([^"]+)")? on disk "([^"]+)"$/ do |parttype, has_label, label, fstype, is_encrypted, luks_password, name|
next if @skip_steps_while_restoring_background
- disk_path = @vm.storage.disk_path(name)
- tails_iso_hybrid = "#{$tmp_dir}/#{File.basename($tails_iso)}"
- begin
- cmd_helper("cp '#{$tails_iso}' '#{tails_iso_hybrid}'")
- cmd_helper("isohybrid '#{tails_iso_hybrid}' --entry 4 --type 0x1c")
- cmd_helper("dd if='#{tails_iso_hybrid}' of='#{disk_path}' conv=notrunc")
- ensure
- cmd_helper("rm -f '#{tails_iso_hybrid}'")
+ opts = {}
+ opts.merge!(:label => label) if has_label
+ opts.merge!(:luks_password => luks_password) if is_encrypted
+ @vm.storage.disk_mkpartfs(name, parttype, fstype, opts)
+end
+
+Given /^I cat an ISO of the Tails image to disk "([^"]+)"$/ do |name|
+ next if @skip_steps_while_restoring_background
+ src_disk = {
+ :path => TAILS_ISO,
+ :opts => {
+ :format => "raw",
+ :readonly => true
+ }
+ }
+ dest_disk = {
+ :path => @vm.storage.disk_path(name),
+ :opts => {
+ :format => @vm.storage.disk_format(name)
+ }
+ }
+ @vm.storage.guestfs_disk_helper(src_disk, dest_disk) do |g, src_disk_handle, dest_disk_handle|
+ g.copy_device_to_device(src_disk_handle, dest_disk_handle, {})
end
end
@@ -33,3 +57,12 @@ Then /^drive "([^"]+)" is not mounted$/ do |name|
assert(!@vm.execute("grep -qs '^#{dev}' /proc/mounts").success?,
"an untrusted partition from drive '#{name}' was automounted")
end
+
+Then /^Tails Greeter has( not)? detected a persistence partition$/ do |no_persistence|
+ next if @skip_steps_while_restoring_background
+ expecting_persistence = no_persistence.nil?
+ @screen.find('TailsGreeter.png')
+ found_persistence = ! @screen.exists('TailsGreeterPersistence.png').nil?
+ assert_equal(expecting_persistence, found_persistence,
+ "Persistence is unexpectedly#{no_persistence} enabled")
+end
diff --git a/features/step_definitions/usb.rb b/features/step_definitions/usb.rb
index 8a65061..0fbff8a 100644
--- a/features/step_definitions/usb.rb
+++ b/features/step_definitions/usb.rb
@@ -1,26 +1,51 @@
-def persistent_mounts
- {
- "cups-configuration" => "/etc/cups",
- "nm-system-connections" => "/etc/NetworkManager/system-connections",
- "claws-mail" => "/home/#{$live_user}/.claws-mail",
- "gnome-keyrings" => "/home/#{$live_user}/.gnome2/keyrings",
- "gnupg" => "/home/#{$live_user}/.gnupg",
- "bookmarks" => "/home/#{$live_user}/.mozilla/firefox/bookmarks",
- "pidgin" => "/home/#{$live_user}/.purple",
- "openssh-client" => "/home/#{$live_user}/.ssh",
- "Persistent" => "/home/#{$live_user}/Persistent",
- "apt/cache" => "/var/cache/apt/archives",
- "apt/lists" => "/var/lib/apt/lists",
+# Returns a hash that for each preset the running Tails is aware of
+# maps the source to the destination.
+def get_persistence_presets(skip_links = false)
+ # Perl script that prints all persistence presets (one per line) on
+ # the form: <mount_point>:<comma-separated-list-of-options>
+ script = <<-EOF
+ use strict;
+ use warnings FATAL => "all";
+ use Tails::Persistence::Configuration::Presets;
+ foreach my $preset (Tails::Persistence::Configuration::Presets->new()->all) {
+ say $preset->destination, ":", join(",", @{$preset->options});
}
+EOF
+ # VMCommand:s cannot handle newlines, and they're irrelevant in the
+ # above perl script any way
+ script.delete!("\n")
+ presets = @vm.execute_successfully("perl -E '#{script}'").stdout.chomp.split("\n")
+ assert presets.size >= 10, "Got #{presets.size} persistence presets, " +
+ "which is too few"
+ persistence_mapping = Hash.new
+ for line in presets
+ destination, options_str = line.split(":")
+ options = options_str.split(",")
+ is_link = options.include? "link"
+ next if is_link and skip_links
+ source_str = options.find { |option| /^source=/.match option }
+ # If no source is given as an option, live-boot's persistence
+ # feature defaults to the destination minus the initial "/".
+ if source_str.nil?
+ source = destination.partition("/").last
+ else
+ source = source_str.split("=")[1]
+ end
+ persistence_mapping[source] = destination
+ end
+ return persistence_mapping
end
-def persistent_volumes_mountpoints
- @vm.execute("ls -1 -d /live/persistence/*_unlocked/").stdout.chomp.split
+def persistent_dirs
+ get_persistence_presets
end
-Given /^I create a new (\d+) ([[:alpha:]]+) USB drive named "([^"]+)"$/ do |size, unit, name|
- next if @skip_steps_while_restoring_background
- @vm.storage.create_new_disk(name, {:size => size, :unit => unit})
+def persistent_mounts
+ get_persistence_presets(true)
+end
+
+def persistent_volumes_mountpoints
+ @vm.execute("ls -1 -d /live/persistence/*_unlocked/").stdout.chomp.split
end
Given /^I clone USB drive "([^"]+)" to a new USB drive "([^"]+)"$/ do |from, to|
@@ -35,7 +60,7 @@ end
Given /^the computer is set to boot from the old Tails DVD$/ do
next if @skip_steps_while_restoring_background
- @vm.set_cdrom_boot($old_tails_iso)
+ @vm.set_cdrom_boot(OLD_TAILS_ISO)
end
Given /^the computer is set to boot in UEFI mode$/ do
@@ -113,7 +138,7 @@ end
Given /^I setup a filesystem share containing the Tails ISO$/ do
next if @skip_steps_while_restoring_background
- @vm.add_share(File.dirname($tails_iso), shared_iso_dir_on_guest)
+ @vm.add_share(File.dirname(TAILS_ISO), shared_iso_dir_on_guest)
end
When /^I do a "Upgrade from ISO" on USB drive "([^"]+)"$/ do |name|
@@ -125,7 +150,7 @@ When /^I do a "Upgrade from ISO" on USB drive "([^"]+)"$/ do |name|
@screen.click(match.getCenter.offset(0, match.h*2))
@screen.wait('USBSelectISO.png', 10)
@screen.wait_and_click('GnomeFileDiagTypeFilename.png', 10)
- iso = "#{shared_iso_dir_on_guest}/#{File.basename($tails_iso)}"
+ iso = "#{shared_iso_dir_on_guest}/#{File.basename(TAILS_ISO)}"
@screen.type(iso + Sikuli::Key.ENTER)
usb_install_helper(name)
end
@@ -133,11 +158,12 @@ end
Given /^I enable all persistence presets$/ do
next if @skip_steps_while_restoring_background
@screen.wait('PersistenceWizardPresets.png', 20)
- # Mark first non-default persistence preset
- @screen.type(Sikuli::Key.TAB*2)
- # Check all non-default persistence presets
- 12.times do
- @screen.type(Sikuli::Key.SPACE + Sikuli::Key.TAB)
+ # Select the "Persistent" folder preset, which is checked by default.
+ @screen.type(Sikuli::Key.TAB)
+ # Check all non-default persistence presets, i.e. all *after* the
+ # "Persistent" folder, which are unchecked by default.
+ (persistent_dirs.size - 1).times do
+ @screen.type(Sikuli::Key.TAB + Sikuli::Key.SPACE)
end
@screen.wait_and_click('PersistenceWizardSave.png', 10)
@screen.wait('PersistenceWizardDone.png', 20)
@@ -179,7 +205,7 @@ def tails_is_installed_helper(name, tails_root, loader)
c = @vm.execute("diff -qr '#{tails_root}/live' '#{target_root}/live'")
assert(c.success?,
- "USB drive '#{name}' has differences in /live:\n#{c.stdout}")
+ "USB drive '#{name}' has differences in /live:\n#{c.stdout}\n#{c.stderr}")
syslinux_files = @vm.execute("ls -1 #{target_root}/syslinux").stdout.chomp.split
# We deal with these files separately
@@ -209,7 +235,7 @@ end
Then /^the ISO's Tails is installed on USB drive "([^"]+)"$/ do |target_name|
next if @skip_steps_while_restoring_background
- iso = "#{shared_iso_dir_on_guest}/#{File.basename($tails_iso)}"
+ iso = "#{shared_iso_dir_on_guest}/#{File.basename(TAILS_ISO)}"
iso_root = "/mnt/iso"
@vm.execute("mkdir -p #{iso_root}")
@vm.execute("mount -o loop #{iso} #{iso_root}")
@@ -278,18 +304,24 @@ end
def tails_persistence_enabled?
persistence_state_file = "/var/lib/live/config/tails.persistence"
return @vm.execute("test -e '#{persistence_state_file}'").success? &&
- @vm.execute('. #{persistence_state_file} && ' +
+ @vm.execute(". '#{persistence_state_file}' && " +
'test "$TAILS_PERSISTENCE_ENABLED" = true').success?
end
-Given /^persistence is enabled$/ do
+Given /^all persistence presets(| from the old Tails version) are enabled$/ do |old_tails|
next if @skip_steps_while_restoring_background
try_for(120, :msg => "Persistence is disabled") do
tails_persistence_enabled?
end
# Check that all persistent directories are mounted
+ if old_tails.empty?
+ expected_mounts = persistent_mounts
+ else
+ assert_not_nil($remembered_persistence_mounts)
+ expected_mounts = $remembered_persistence_mounts
+ end
mount = @vm.execute("mount").stdout.chomp
- for _, dir in persistent_mounts do
+ for _, dir in expected_mounts do
assert(mount.include?("on #{dir} "),
"Persistent directory '#{dir}' is not mounted")
end
@@ -322,9 +354,16 @@ def boot_device_type
return boot_dev_type
end
-Then /^Tails is running from USB drive "([^"]+)"$/ do |name|
+Then /^Tails is running from (.*) drive "([^"]+)"$/ do |bus, name|
next if @skip_steps_while_restoring_background
- assert_equal("usb", boot_device_type)
+ bus = bus.downcase
+ case bus
+ when "ide"
+ expected_bus = "ata"
+ else
+ expected_bus = bus
+ end
+ assert_equal(expected_bus, boot_device_type)
actual_dev = boot_device
# The boot partition differs between a "normal" install using the
# USB installer and isohybrid installations
@@ -332,7 +371,7 @@ Then /^Tails is running from USB drive "([^"]+)"$/ do |name|
expected_dev_isohybrid = @vm.disk_dev(name) + "4"
assert(actual_dev == expected_dev_normal ||
actual_dev == expected_dev_isohybrid,
- "We are running from device #{actual_dev}, but for USB drive " +
+ "We are running from device #{actual_dev}, but for #{bus} drive " +
"'#{name}' we expected to run from either device " +
"#{expected_dev_normal} (when installed via the USB installer) " +
"or #{expected_dev_normal} (when installed from an isohybrid)")
@@ -371,7 +410,7 @@ Then /^the boot device has safe access rights$/ do
"Boot device '#{super_boot_dev}' is not system internal for udisks")
end
-Then /^persistent filesystems have safe access rights$/ do
+Then /^all persistent filesystems have safe access rights$/ do
persistent_volumes_mountpoints.each do |mountpoint|
fs_owner = @vm.execute("stat -c %U #{mountpoint}").stdout.chomp
fs_group = @vm.execute("stat -c %G #{mountpoint}").stdout.chomp
@@ -382,7 +421,7 @@ Then /^persistent filesystems have safe access rights$/ do
end
end
-Then /^persistence configuration files have safe access rights$/ do
+Then /^all persistence configuration files have safe access rights$/ do
persistent_volumes_mountpoints.each do |mountpoint|
assert(@vm.execute("test -e #{mountpoint}/persistence.conf").success?,
"#{mountpoint}/persistence.conf does not exist, while it should")
@@ -401,19 +440,33 @@ Then /^persistence configuration files have safe access rights$/ do
end
end
-Then /^persistent directories have safe access rights$/ do
+Then /^all persistent directories(| from the old Tails version) have safe access rights$/ do |old_tails|
next if @skip_steps_while_restoring_background
- expected_perms = "700"
+ if old_tails.empty?
+ expected_dirs = persistent_dirs
+ else
+ assert_not_nil($remembered_persistence_dirs)
+ expected_dirs = $remembered_persistence_dirs
+ end
persistent_volumes_mountpoints.each do |mountpoint|
- # We also want to check that dotfiles' source has safe permissions
- all_persistent_dirs = persistent_mounts.clone
- all_persistent_dirs["dotfiles"] = "/home/#{$live_user}/"
- persistent_mounts.each do |src, dest|
- next unless dest.start_with?("/home/#{$live_user}/")
- f = "#{mountpoint}/#{src}"
- next unless @vm.execute("test -d #{f}").success?
- file_perms = @vm.execute("stat -c %a '#{f}'").stdout.chomp
- assert_equal(expected_perms, file_perms)
+ expected_dirs.each do |src, dest|
+ full_src = "#{mountpoint}/#{src}"
+ assert_vmcommand_success @vm.execute("test -d #{full_src}")
+ dir_perms = @vm.execute_successfully("stat -c %a '#{full_src}'").stdout.chomp
+ dir_owner = @vm.execute_successfully("stat -c %U '#{full_src}'").stdout.chomp
+ if dest.start_with?("/home/#{LIVE_USER}")
+ expected_perms = "700"
+ expected_owner = LIVE_USER
+ else
+ expected_perms = "755"
+ expected_owner = "root"
+ end
+ assert_equal(expected_perms, dir_perms,
+ "Persistent source #{full_src} has permission " \
+ "#{dir_perms}, expected #{expected_perms}")
+ assert_equal(expected_owner, dir_owner,
+ "Persistent source #{full_src} has owner " \
+ "#{dir_owner}, expected #{expected_owner}")
end
end
end
@@ -445,9 +498,21 @@ When /^I write some files not expected to persist$/ do
end
end
-Then /^the expected persistent files are present in the filesystem$/ do
+When /^I take note of which persistence presets are available$/ do
next if @skip_steps_while_restoring_background
- persistent_mounts.each do |_, dir|
+ $remembered_persistence_mounts = persistent_mounts
+ $remembered_persistence_dirs = persistent_dirs
+end
+
+Then /^the expected persistent files(| created with the old Tails version) are present in the filesystem$/ do |old_tails|
+ next if @skip_steps_while_restoring_background
+ if old_tails.empty?
+ expected_mounts = persistent_mounts
+ else
+ assert_not_nil($remembered_persistence_mounts)
+ expected_mounts = $remembered_persistence_mounts
+ end
+ expected_mounts.each do |_, dir|
assert(@vm.execute("test -e #{dir}/XXX_persist").success?,
"Could not find expected file in persistent directory #{dir}")
assert(!@vm.execute("test -e #{dir}/XXX_gone").success?,
@@ -455,20 +520,43 @@ Then /^the expected persistent files are present in the filesystem$/ do
end
end
-Then /^only the expected files should persist on USB drive "([^"]+)"$/ do |name|
+Then /^only the expected files are present on the persistence partition encrypted with password "([^"]+)" on USB drive "([^"]+)"$/ do |password, name|
next if @skip_steps_while_restoring_background
- step "a computer"
- step "the computer is set to boot from USB drive \"#{name}\""
- step "the network is unplugged"
- step "I start the computer"
- step "the computer boots Tails"
- step "I enable read-only persistence with password \"asdf\""
- step "I log in to a new session"
- step "persistence is enabled"
- step "GNOME has started"
- step "all notifications have disappeared"
- step "the expected persistent files are present in the filesystem"
- step "I shutdown Tails and wait for the computer to power off"
+ assert(!@vm.is_running?)
+ disk = {
+ :path => @vm.storage.disk_path(name),
+ :opts => {
+ :format => @vm.storage.disk_format(name),
+ :readonly => true
+ }
+ }
+ @vm.storage.guestfs_disk_helper(disk) do |g, disk_handle|
+ partitions = g.part_list(disk_handle).map do |part_desc|
+ disk_handle + part_desc["part_num"].to_s
+ end
+ partition = partitions.find do |part|
+ g.blkid(part)["PART_ENTRY_NAME"] == "TailsData"
+ end
+ assert_not_nil(partition, "Could not find the 'TailsData' partition " \
+ "on disk '#{disk_handle}'")
+ luks_mapping = File.basename(partition) + "_unlocked"
+ g.luks_open(partition, password, luks_mapping)
+ luks_dev = "/dev/mapper/#{luks_mapping}"
+ mount_point = "/"
+ g.mount(luks_dev, mount_point)
+ assert_not_nil($remembered_persistence_mounts)
+ $remembered_persistence_mounts.each do |dir, _|
+ # Guestfs::exists may have a bug; if the file exists, 1 is
+ # returned, but if it doesn't exist false is returned. It seems
+ # the translation of C types into Ruby types is glitchy.
+ assert(g.exists("/#{dir}/XXX_persist") == 1,
+ "Could not find expected file in persistent directory #{dir}")
+ assert(g.exists("/#{dir}/XXX_gone") != 1,
+ "Found file that should not have persisted in persistent directory #{dir}")
+ end
+ g.umount(mount_point)
+ g.luks_close(luks_dev)
+ end
end
When /^I delete the persistent partition$/ do
@@ -484,3 +572,8 @@ Then /^Tails has started in UEFI mode$/ do
assert(@vm.execute("test -d /sys/firmware/efi").success?,
"/sys/firmware/efi does not exist")
end
+
+Given /^I create a ([[:alpha:]]+) label on disk "([^"]+)"$/ do |type, name|
+ next if @skip_steps_while_restoring_background
+ @vm.storage.disk_mklabel(name, type)
+end
diff --git a/features/support/config.rb b/features/support/config.rb
index b5f6fcd..f28cd26 100644
--- a/features/support/config.rb
+++ b/features/support/config.rb
@@ -1,28 +1,46 @@
require 'fileutils'
+require 'yaml'
require "#{Dir.pwd}/features/support/helpers/misc_helpers.rb"
-# Dynamic
-$tails_iso = ENV['ISO'] || get_newest_iso
-$old_tails_iso = ENV['OLD_ISO'] || get_oldest_iso
-$tmp_dir = ENV['TEMP_DIR'] || "/tmp/TailsToaster"
-$vm_xml_path = ENV['VM_XML_PATH'] || "#{Dir.pwd}/features/domains"
-$misc_files_dir = "#{Dir.pwd}/features/misc_files"
-$keep_snapshots = !ENV['KEEP_SNAPSHOTS'].nil?
-$x_display = ENV['DISPLAY']
-$debug = !ENV['DEBUG'].nil?
-$pause_on_fail = !ENV['PAUSE_ON_FAIL'].nil?
-$time_at_start = Time.now
-$live_user = cmd_helper(". config/chroot_local-includes/etc/live/config.d/username.conf; echo ${LIVE_USERNAME}").chomp
-$sikuli_retry_findfailed = !ENV['SIKULI_RETRY_FINDFAILED'].nil?
+# These two files deal with options like some of the settings passed
+# to the `run_test_suite` script, and "secrets" like credentials
+# (passwords, SSH keys) to be used in tests.
+DEFAULTS_CONFIG_FILE = "#{Dir.pwd}/features/config/defaults.yml"
+LOCAL_CONFIG_FILE = "#{Dir.pwd}/features/config/local.yml"
-# Static
-$configured_keyserver_hostname = 'hkps.pool.sks-keyservers.net'
-$services_expected_on_all_ifaces =
+assert File.exists?(DEFAULTS_CONFIG_FILE)
+$config = YAML.load(File.read(DEFAULTS_CONFIG_FILE))
+if File.exists?(LOCAL_CONFIG_FILE)
+ $config.merge!(YAML.load(File.read(LOCAL_CONFIG_FILE)))
+end
+# Options passed to the `run_test_suite` script will always take
+# precedence. The way we import these keys is only safe for values
+# with types boolean or string. If we need more, we'll have to invoke
+# YAML's type autodetection on ENV some how.
+$config.merge!(ENV)
+
+# Dynamic constants initialized through the environment or similar,
+# e.g. options we do not want to be configurable through the YAML
+# configuration files.
+DISPLAY = ENV['DISPLAY']
+GIT_DIR = ENV['PWD']
+KEEP_SNAPSHOTS = !ENV['KEEP_SNAPSHOTS'].nil?
+LIVE_USER = cmd_helper(". config/chroot_local-includes/etc/live/config.d/username.conf; echo ${LIVE_USERNAME}").chomp
+OLD_TAILS_ISO = ENV['OLD_TAILS_ISO']
+TAILS_ISO = ENV['TAILS_ISO']
+TIME_AT_START = Time.now
+
+# Constants that are statically initialized.
+CONFIGURED_KEYSERVER_HOSTNAME = 'hkps.pool.sks-keyservers.net'
+MISC_FILES_DIR = "#{Dir.pwd}/features/misc_files"
+SERVICES_EXPECTED_ON_ALL_IFACES =
[
["cupsd", "0.0.0.0", "631"],
["dhclient", "0.0.0.0", "*"]
]
-$tor_authorities =
+# OpenDNS
+SOME_DNS_SERVER = "208.67.222.222"
+TOR_AUTHORITIES =
# List grabbed from Tor's sources, src/or/config.c:~750.
[
"128.31.0.39", "86.59.21.38", "194.109.206.212",
@@ -30,5 +48,4 @@ $tor_authorities =
"193.23.244.244", "208.83.223.34", "171.25.193.9",
"154.35.32.5"
]
-# OpenDNS
-$some_dns_server = "208.67.222.222"
+VM_XML_PATH = "#{Dir.pwd}/features/domains"
diff --git a/features/support/helpers/exec_helper.rb b/features/support/helpers/exec_helper.rb
index b0d3a9c..400b0ae 100644
--- a/features/support/helpers/exec_helper.rb
+++ b/features/support/helpers/exec_helper.rb
@@ -11,12 +11,10 @@ class VMCommand
end
def VMCommand.wait_until_remote_shell_is_up(vm, timeout = 30)
- begin
- Timeout::timeout(timeout) do
- VMCommand.execute(vm, "true", { :user => "root", :spawn => false })
+ try_for(30, :msg => "Remote shell seems to be down") do
+ Timeout::timeout(3) do
+ VMCommand.execute(vm, "echo 'hello?'")
end
- rescue Timeout::Error
- raise "Remote shell seems to be down"
end
end
@@ -34,14 +32,14 @@ class VMCommand
options[:spawn] ||= false
type = options[:spawn] ? "spawn" : "call"
socket = TCPSocket.new("127.0.0.1", vm.get_remote_shell_port)
- STDERR.puts "#{type}ing as #{options[:user]}: #{cmd}" if $debug
+ STDERR.puts "#{type}ing as #{options[:user]}: #{cmd}" if $config["DEBUG"]
begin
socket.puts(JSON.dump([type, options[:user], cmd]))
s = socket.readline(sep = "\0").chomp("\0")
ensure
socket.close
end
- STDERR.puts "#{type} returned: #{s}" if $debug
+ STDERR.puts "#{type} returned: #{s}" if $config["DEBUG"]
begin
return JSON.load(s)
rescue JSON::ParserError
@@ -58,4 +56,12 @@ class VMCommand
return @returncode == 0
end
+ def to_s
+ "Return status: #{@returncode}\n" +
+ "STDOUT:\n" +
+ @stdout +
+ "STDERR:\n" +
+ @stderr
+ end
+
end
diff --git a/features/support/helpers/firewall_helper.rb b/features/support/helpers/firewall_helper.rb
index 400965a..04e6853 100644
--- a/features/support/helpers/firewall_helper.rb
+++ b/features/support/helpers/firewall_helper.rb
@@ -36,9 +36,9 @@ end
class FirewallLeakCheck
attr_reader :ipv4_tcp_leaks, :ipv4_nontcp_leaks, :ipv6_leaks, :nonip_leaks
- def initialize(pcap_file, tor_relays)
- packets = PacketFu::PcapFile.new.file_to_array(:filename => pcap_file)
- @tor_relays = tor_relays
+ def initialize(pcap_file, hosts)
+ @pcap_file = pcap_file
+ packets = PacketFu::PcapFile.new.file_to_array(:filename => @pcap_file)
ipv4_tcp_packets = []
ipv4_nontcp_packets = []
ipv6_packets = []
@@ -58,13 +58,19 @@ class FirewallLeakCheck
end
end
ipv4_tcp_hosts = get_public_hosts_from_ippackets ipv4_tcp_packets
- tor_nodes = Set.new(get_all_tor_contacts)
- @ipv4_tcp_leaks = ipv4_tcp_hosts.select{|host| !tor_nodes.member?(host)}
+ accepted = Set.new(hosts)
+ @ipv4_tcp_leaks = ipv4_tcp_hosts.select { |host| !accepted.member?(host) }
@ipv4_nontcp_leaks = get_public_hosts_from_ippackets ipv4_nontcp_packets
@ipv6_leaks = get_public_hosts_from_ippackets ipv6_packets
@nonip_leaks = nonip_packets
end
+ def save_pcap_file
+ pcap_copy = "#{@pcap_file}-#{DateTime.now}"
+ FileUtils.cp(@pcap_file, pcap_copy)
+ puts "Full network capture available at: #{pcap_copy}"
+ end
+
# Returns a list of all unique non-LAN destination IP addresses
# found in `packets`.
def get_public_hosts_from_ippackets(packets)
@@ -87,14 +93,27 @@ class FirewallLeakCheck
hosts.uniq
end
- # Returns an array of all Tor relays and authorities, i.e. all
- # Internet hosts Tails ever should contact.
- def get_all_tor_contacts
- @tor_relays + $tor_authorities
- end
-
- def empty?
- @ipv4_tcp_leaks.empty? and @ipv4_nontcp_leaks.empty? and @ipv6_leaks.empty? and @nonip_leaks.empty?
+ def assert_no_leaks
+ err = ""
+ if !@ipv4_tcp_leaks.empty?
+ err += "The following IPv4 TCP non-Tor Internet hosts were " +
+ "contacted:\n" + ipv4_tcp_leaks.join("\n")
+ end
+ if !@ipv4_nontcp_leaks.empty?
+ err += "The following IPv4 non-TCP Internet hosts were contacted:\n" +
+ ipv4_nontcp_leaks.join("\n")
+ end
+ if !@ipv6_leaks.empty?
+ err += "The following IPv6 Internet hosts were contacted:\n" +
+ ipv6_leaks.join("\n")
+ end
+ if !@nonip_leaks.empty?
+ err += "Some non-IP packets were sent\n"
+ end
+ if !err.empty?
+ save_pcap_file
+ raise err
+ end
end
end
diff --git a/features/support/helpers/misc_helpers.rb b/features/support/helpers/misc_helpers.rb
index caf64b8..d315e52 100644
--- a/features/support/helpers/misc_helpers.rb
+++ b/features/support/helpers/misc_helpers.rb
@@ -23,6 +23,8 @@ def try_for(t, options = {})
loop do
begin
return true if yield
+ rescue NameError => e
+ raise e
rescue Timeout::Error => e
if options[:msg]
raise RuntimeError, options[:msg], caller
@@ -45,7 +47,7 @@ def try_for(t, options = {})
end
def wait_until_tor_is_working
- try_for(240) { @vm.execute(
+ try_for(270) { @vm.execute(
'. /usr/local/lib/tails-shell-library/tor.sh; tor_is_working').success? }
end
@@ -88,34 +90,24 @@ def cmd_helper(cmd)
end
end
-def tails_iso_creation_date(path)
- label = cmd_helper("/sbin/blkid -p -s LABEL -o value #{path}")
- assert(label[/^TAILS \d+(\.\d+)+(~rc\d+)? - \d+$/],
- "Got invalid label '#{label}' from Tails image '#{path}'")
- return label[/\d+$/]
-end
-
-def sort_isos_by_creation_date
- Dir.glob("#{Dir.pwd}/*.iso").sort_by {|f| tails_iso_creation_date(f)}
-end
-
-def get_newest_iso
- return sort_isos_by_creation_date.last
-end
-
-def get_oldest_iso
- return sort_isos_by_creation_date.first
-end
-
# This command will grab all router IP addresses from the Tor
-# consensus in the VM.
-def get_tor_relays
+# consensus in the VM + the hardcoded TOR_AUTHORITIES.
+def get_all_tor_nodes
cmd = 'awk "/^r/ { print \$6 }" /var/lib/tor/cached-microdesc-consensus'
- @vm.execute(cmd).stdout.chomp.split("\n")
+ @vm.execute(cmd).stdout.chomp.split("\n") + TOR_AUTHORITIES
end
-def save_pcap_file
- pcap_copy = "#{$tmp_dir}/pcap_with_leaks-#{DateTime.now}"
- FileUtils.cp(@sniffer.pcap_file, pcap_copy)
- puts "Full network capture available at: #{pcap_copy}"
+def get_free_space(machine, path)
+ case machine
+ when 'host'
+ assert(File.exists?(path), "Path '#{path}' not found on #{machine}.")
+ free = cmd_helper("df '#{path}'")
+ when 'guest'
+ assert(@vm.file_exist?(path), "Path '#{path}' not found on #{machine}.")
+ free = @vm.execute_successfully("df '#{path}'")
+ else
+ raise 'Unsupported machine type #{machine} passed.'
+ end
+ output = free.split("\n").last
+ return output.match(/[^\s]\s+[0-9]+\s+[0-9]+\s+([0-9]+)\s+.*/)[1].chomp.to_i
end
diff --git a/features/support/helpers/sikuli_helper.rb b/features/support/helpers/sikuli_helper.rb
index 4e8b784..b0ad6da 100644
--- a/features/support/helpers/sikuli_helper.rb
+++ b/features/support/helpers/sikuli_helper.rb
@@ -64,7 +64,7 @@ $_original_sikuli_screen_new ||= Sikuli::Screen.method :new
def sikuli_script_proxy.new(*args)
s = $_original_sikuli_screen_new.call(*args)
- if $sikuli_retry_findfailed
+ if $config["SIKULI_RETRY_FINDFAILED"]
# The usage of `_invoke()` below exemplifies how one can wrap
# around Java objects' methods when they're imported using RJB. It
# isn't pretty. The seconds argument is the parameter signature,
@@ -112,6 +112,10 @@ def sikuli_script_proxy.new(*args)
self.doubleClick(self.wait(pic, time))
end
+ def s.wait_and_right_click(pic, time)
+ self.rightClick(self.wait(pic, time))
+ end
+
def s.wait_and_hover(pic, time)
self.hover(self.wait(pic, time))
end
@@ -137,13 +141,13 @@ java.lang.System.setProperty("SIKULI_IMAGE_PATH", "#{Dir.pwd}/features/images/")
# required, ruby's require method complains that the method for the
# field accessor is missing.
sikuli_settings = Sikuli::Settings.new
-sikuli_settings.OcrDataPath = $tmp_dir
+sikuli_settings.OcrDataPath = $config["TMP_DIR"]
# sikuli_ruby, which we used before, defaulted to 0.9 minimum
# similarity, so all our current images are adapted to that value.
# Also, Sikuli's default of 0.7 is simply too low (many false
# positives).
sikuli_settings.MinSimilarity = 0.9
-sikuli_settings.ActionLogs = $debug
-sikuli_settings.DebugLogs = $debug
-sikuli_settings.InfoLogs = $debug
-sikuli_settings.ProfileLogs = $debug
+sikuli_settings.ActionLogs = $config["DEBUG"]
+sikuli_settings.DebugLogs = $config["DEBUG"]
+sikuli_settings.InfoLogs = $config["DEBUG"]
+sikuli_settings.ProfileLogs = $config["DEBUG"]
diff --git a/features/support/helpers/net_helper.rb b/features/support/helpers/sniffing_helper.rb
index 2911919..48e028d 100644
--- a/features/support/helpers/net_helper.rb
+++ b/features/support/helpers/sniffing_helper.rb
@@ -14,15 +14,14 @@ class Sniffer
attr_reader :name, :pcap_file, :pid
- def initialize(name, bridge_name)
+ def initialize(name, vmnet)
@name = name
- @bridge_name = bridge_name
- @bridge_mac = File.open("/sys/class/net/#{@bridge_name}/address", "rb").read.chomp
- @pcap_file = "#{$tmp_dir}/#{name}.pcap"
+ @vmnet = vmnet
+ @pcap_file = "#{$config["TMP_DIR"]}/#{name}.pcap"
end
- def capture(filter="not ether src host #{@bridge_mac} and not ether proto \\arp and not ether proto \\rarp")
- job = IO.popen("/usr/sbin/tcpdump -n -i #{@bridge_name} -w #{@pcap_file} -U '#{filter}' >/dev/null 2>&1")
+ def capture(filter="not ether src host #{@vmnet.bridge_mac} and not ether proto \\arp and not ether proto \\rarp")
+ job = IO.popen("/usr/sbin/tcpdump -n -i #{@vmnet.bridge_name} -w #{@pcap_file} -U '#{filter}' >/dev/null 2>&1")
@pid = job.pid
end
diff --git a/features/support/helpers/storage_helper.rb b/features/support/helpers/storage_helper.rb
index 80a1e1e..35b6dff 100644
--- a/features/support/helpers/storage_helper.rb
+++ b/features/support/helpers/storage_helper.rb
@@ -7,28 +7,27 @@
# sense.
require 'libvirt'
+require 'guestfs'
require 'rexml/document'
require 'etc'
class VMStorage
- @@virt = nil
-
def initialize(virt, xml_path)
- @@virt ||= virt
+ @virt = virt
@xml_path = xml_path
pool_xml = REXML::Document.new(File.read("#{@xml_path}/storage_pool.xml"))
pool_name = pool_xml.elements['pool/name'].text
begin
- @pool = @@virt.lookup_storage_pool_by_name(pool_name)
+ @pool = @virt.lookup_storage_pool_by_name(pool_name)
rescue Libvirt::RetrieveError
# There's no pool with that name, so we don't have to clear it
else
VMStorage.clear_storage_pool(@pool)
end
- @pool_path = "#{$tmp_dir}/#{pool_name}"
+ @pool_path = "#{$config["TMP_DIR"]}/#{pool_name}"
pool_xml.elements['pool/target/path'].text = @pool_path
- @pool = @@virt.define_storage_pool_xml(pool_xml.to_s)
+ @pool = @virt.define_storage_pool_xml(pool_xml.to_s)
@pool.build
@pool.create
@pool.refresh
@@ -69,6 +68,15 @@ class VMStorage
options[:size] ||= 2
options[:unit] ||= "GiB"
options[:type] ||= "qcow2"
+ # Require 'slightly' more space to be available to give a bit more leeway
+ # with rounding, temp file creation, etc.
+ reserved = 500
+ needed = convert_to_MiB(options[:size].to_i, options[:unit])
+ avail = convert_to_MiB(get_free_space('host', @pool_path), "KiB")
+ assert(avail - reserved >= needed,
+ "Error creating disk \"#{name}\" in \"#{@pool_path}\". " \
+ "Need #{needed} MiB but only #{avail} MiB is available of " \
+ "which #{reserved} MiB is reserved for other temporary files.")
begin
old_vol = @pool.lookup_volume_by_name(name)
rescue Libvirt::RetrieveError
@@ -116,28 +124,70 @@ class VMStorage
@pool.lookup_volume_by_name(name).path
end
- # We use parted for the disk_mk* functions since it can format
- # partitions "inside" the super block device; mkfs.* need a
- # partition device (think /dev/sdaX), so we'd have to use something
- # like losetup or kpartx, which would require administrative
- # privileges. These functions only work for raw disk images.
-
- # TODO: We should switch to guestfish/libguestfs (which has
- # ruby-bindings) so we could use qcow2 instead of raw, and more
- # easily use LVM volumes.
-
- # For type, see label-type for mklabel in parted(8)
- def disk_mklabel(name, type)
- assert_equal("raw", disk_format(name))
- path = disk_path(name)
- cmd_helper("/sbin/parted -s '#{path}' mklabel #{type}")
+ def disk_mklabel(name, parttype)
+ disk = {
+ :path => disk_path(name),
+ :opts => {
+ :format => disk_format(name)
+ }
+ }
+ guestfs_disk_helper(disk) do |g, disk_handle|
+ g.part_init(disk_handle, parttype)
+ end
+ end
+
+ def disk_mkpartfs(name, parttype, fstype, opts = {})
+ opts[:label] ||= nil
+ opts[:luks_password] ||= nil
+ disk = {
+ :path => disk_path(name),
+ :opts => {
+ :format => disk_format(name)
+ }
+ }
+ guestfs_disk_helper(disk) do |g, disk_handle|
+ g.part_disk(disk_handle, parttype)
+ g.part_set_name(disk_handle, 1, opts[:label]) if opts[:label]
+ primary_partition = g.list_partitions()[0]
+ if opts[:luks_password]
+ g.luks_format(primary_partition, opts[:luks_password], 0)
+ luks_mapping = File.basename(primary_partition) + "_unlocked"
+ g.luks_open(primary_partition, opts[:luks_password], luks_mapping)
+ luks_dev = "/dev/mapper/#{luks_mapping}"
+ g.mkfs(fstype, luks_dev)
+ g.luks_close(luks_dev)
+ else
+ g.mkfs(fstype, primary_partition)
+ end
+ end
end
- # For fstype, see fs-type for mkfs in parted(8)
- def disk_mkpartfs(name, fstype)
- assert(disk_format(name), "raw")
- path = disk_path(name)
- cmd_helper("/sbin/parted -s '#{path}' mkpartfs primary '#{fstype}' 0% 100%")
+ def disk_mkswap(name, parttype)
+ disk = {
+ :path => disk_path(name),
+ :opts => {
+ :format => disk_format(name)
+ }
+ }
+ guestfs_disk_helper(disk) do |g, disk_handle|
+ g.part_disk(disk_handle, parttype)
+ primary_partition = g.list_partitions()[0]
+ g.mkswap(primary_partition)
+ end
+ end
+
+ def guestfs_disk_helper(*disks)
+ assert(block_given?)
+ g = Guestfs::Guestfs.new()
+ g.set_trace(1) if $config["DEBUG"]
+ g.set_autosync(1)
+ disks.each do |disk|
+ g.add_drive_opts(disk[:path], disk[:opts])
+ end
+ g.launch()
+ yield(g, *g.list_devices())
+ ensure
+ g.close
end
end
diff --git a/features/support/helpers/vm_helper.rb b/features/support/helpers/vm_helper.rb
index 2b5ad29..9cdec42 100644
--- a/features/support/helpers/vm_helper.rb
+++ b/features/support/helpers/vm_helper.rb
@@ -1,77 +1,83 @@
require 'libvirt'
require 'rexml/document'
-class VM
+class VMNet
+
+ attr_reader :net_name, :net
+
+ def initialize(virt, xml_path)
+ @virt = virt
+ net_xml = File.read("#{xml_path}/default_net.xml")
+ update(net_xml)
+ rescue Exception => e
+ destroy_and_undefine
+ raise e
+ end
+
+ # We lookup by name so we also catch networks from previous test
+ # suite runs that weren't properly cleaned up (e.g. aborted).
+ def destroy_and_undefine
+ begin
+ old_net = @virt.lookup_network_by_name(@net_name)
+ old_net.destroy if old_net.active?
+ old_net.undefine
+ rescue
+ end
+ end
- # These class attributes will be lazily initialized during the first
- # instantiation:
- # This is the libvirt connection, of which we only want one and
- # which can persist for different VM instances (even in parallel)
- @@virt = nil
- # This is a storage helper that deals with volume manipulation. The
- # storage it deals with persists across VMs, by necessity.
- @@storage = nil
+ def update(xml)
+ net_xml = REXML::Document.new(xml)
+ @net_name = net_xml.elements['network/name'].text
+ destroy_and_undefine
+ @net = @virt.define_network_xml(xml)
+ @net.create
+ end
- def VM.storage
- return @@storage
+ def bridge_name
+ @net.bridge_name
end
- def storage
- return @@storage
+ def bridge_mac
+ File.open("/sys/class/net/#{bridge_name}/address", "rb").read.chomp
end
- attr_reader :domain, :display, :ip, :net
+end
+
+
+class VM
+
+ attr_reader :domain, :display, :vmnet, :storage
- def initialize(xml_path, x_display)
- @@virt ||= Libvirt::open("qemu:///system")
+ def initialize(virt, xml_path, vmnet, storage, x_display)
+ @virt = virt
@xml_path = xml_path
+ @vmnet = vmnet
+ @storage = storage
default_domain_xml = File.read("#{@xml_path}/default.xml")
- update_domain(default_domain_xml)
- default_net_xml = File.read("#{@xml_path}/default_net.xml")
- update_net(default_net_xml)
+ update(default_domain_xml)
@display = Display.new(@domain_name, x_display)
- set_cdrom_boot($tails_iso)
+ set_cdrom_boot(TAILS_ISO)
plug_network
- # unlike the domain and net the storage pool should survive VM
- # teardown (so a new instance can use e.g. a previously created
- # USB drive), so we only create a new one if there is none.
- @@storage ||= VMStorage.new(@@virt, xml_path)
rescue Exception => e
- clean_up_net
- clean_up_domain
+ destroy_and_undefine
raise e
end
- def update_domain(xml)
+ def update(xml)
domain_xml = REXML::Document.new(xml)
@domain_name = domain_xml.elements['domain/name'].text
- clean_up_domain
- @domain = @@virt.define_domain_xml(xml)
+ destroy_and_undefine
+ @domain = @virt.define_domain_xml(xml)
end
- def update_net(xml)
- net_xml = REXML::Document.new(xml)
- @net_name = net_xml.elements['network/name'].text
- @ip = net_xml.elements['network/ip/dhcp/host/'].attributes['ip']
- clean_up_net
- @net = @@virt.define_network_xml(xml)
- @net.create
- end
-
- def clean_up_domain
- begin
- domain = @@virt.lookup_domain_by_name(@domain_name)
- domain.destroy if domain.active?
- domain.undefine
- rescue
- end
- end
-
- def clean_up_net
+ # We lookup by name so we also catch domains from previous test
+ # suite runs that weren't properly cleaned up (e.g. aborted).
+ def destroy_and_undefine
begin
- net = @@virt.lookup_network_by_name(@net_name)
- net.destroy if net.active?
- net.undefine
+ old_domain = @virt.lookup_domain_by_name(@domain_name)
+ old_domain.destroy if old_domain.active?
+ old_domain.undefine
+ @display.stop if @display && @display.active?
rescue
end
end
@@ -82,7 +88,7 @@ class VM
if is_running?
@domain.update_device(domain_xml.elements['domain/devices/interface'].to_s)
else
- update_domain(domain_xml.to_s)
+ update(domain_xml.to_s)
end
end
@@ -102,7 +108,7 @@ class VM
if is_running?
@domain.update_device(e.to_s)
else
- update_domain(domain_xml.to_s)
+ update(domain_xml.to_s)
end
end
end
@@ -122,7 +128,7 @@ class VM
end
domain_xml = REXML::Document.new(@domain.xml_desc)
domain_xml.elements['domain/os/boot'].attributes['dev'] = dev
- update_domain(domain_xml.to_s)
+ update(domain_xml.to_s)
end
def set_cdrom_image(image)
@@ -136,7 +142,7 @@ class VM
if is_running?
@domain.update_device(e.to_s, Libvirt::Domain::DEVICE_MODIFY_FORCE)
else
- update_domain(domain_xml.to_s)
+ update(domain_xml.to_s)
end
end
end
@@ -156,6 +162,15 @@ class VM
end
def plug_drive(name, type)
+ removable_usb = nil
+ case type
+ when "removable usb", "usb"
+ type = "usb"
+ removable_usb = "on"
+ when "non-removable usb"
+ type = "usb"
+ removable_usb = "off"
+ end
# Get the next free /dev/sdX on guest
used_devs = []
domain_xml = REXML::Document.new(@domain.xml_desc)
@@ -171,20 +186,18 @@ class VM
assert letter <= 'z'
xml = REXML::Document.new(File.read("#{@xml_path}/disk.xml"))
- xml.elements['disk/source'].attributes['file'] = @@storage.disk_path(name)
- xml.elements['disk/driver'].attributes['type'] = @@storage.disk_format(name)
+ xml.elements['disk/source'].attributes['file'] = @storage.disk_path(name)
+ xml.elements['disk/driver'].attributes['type'] = @storage.disk_format(name)
xml.elements['disk/target'].attributes['dev'] = dev
xml.elements['disk/target'].attributes['bus'] = type
- if type == "usb"
- xml.elements['disk/target'].attributes['removable'] = 'on'
- end
+ xml.elements['disk/target'].attributes['removable'] = removable_usb if removable_usb
if is_running?
@domain.attach_device(xml.to_s)
else
domain_xml = REXML::Document.new(@domain.xml_desc)
domain_xml.elements['domain/devices'].add_element(xml)
- update_domain(domain_xml.to_s)
+ update(domain_xml.to_s)
end
end
@@ -192,7 +205,7 @@ class VM
domain_xml = REXML::Document.new(@domain.xml_desc)
domain_xml.elements.each('domain/devices/disk') do |e|
begin
- if e.elements['source'].attribute('file').to_s == @@storage.disk_path(name)
+ if e.elements['source'].attribute('file').to_s == @storage.disk_path(name)
return e.to_s
end
rescue
@@ -238,7 +251,7 @@ class VM
xml.elements['filesystem/target'].attributes['dir'] = tag
domain_xml = REXML::Document.new(@domain.xml_desc)
domain_xml.elements['domain/devices'].add_element(xml)
- update_domain(domain_xml.to_s)
+ update(domain_xml.to_s)
end
def list_shares
@@ -257,7 +270,7 @@ class VM
domain_xml.elements['domain/memory'].attributes['unit'] = unit
domain_xml.elements['domain/currentMemory'].text = size
domain_xml.elements['domain/currentMemory'].attributes['unit'] = unit
- update_domain(domain_xml.to_s)
+ update(domain_xml.to_s)
end
def get_ram_size_in_bytes
@@ -271,21 +284,21 @@ class VM
raise "System architecture can only be set to inactice vms" if is_running?
domain_xml = REXML::Document.new(@domain.xml_desc)
domain_xml.elements['domain/os/type'].attributes['arch'] = arch
- update_domain(domain_xml.to_s)
+ update(domain_xml.to_s)
end
def add_hypervisor_feature(feature)
raise "Hypervisor features can only be added to inactice vms" if is_running?
domain_xml = REXML::Document.new(@domain.xml_desc)
domain_xml.elements['domain/features'].add_element(feature)
- update_domain(domain_xml.to_s)
+ update(domain_xml.to_s)
end
def drop_hypervisor_feature(feature)
raise "Hypervisor features can only be fropped from inactice vms" if is_running?
domain_xml = REXML::Document.new(@domain.xml_desc)
domain_xml.elements['domain/features'].delete_element(feature)
- update_domain(domain_xml.to_s)
+ update(domain_xml.to_s)
end
def disable_pae_workaround
@@ -300,7 +313,7 @@ class VM
EOF
domain_xml = REXML::Document.new(@domain.xml_desc)
domain_xml.elements['domain'].add_element(REXML::Document.new(xml))
- update_domain(domain_xml.to_s)
+ update(domain_xml.to_s)
end
def set_os_loader(type)
@@ -312,7 +325,7 @@ EOF
domain_xml.elements['domain/os'].add_element(REXML::Document.new(
'<loader>/usr/share/ovmf/OVMF.fd</loader>'
))
- update_domain(domain_xml.to_s)
+ update(domain_xml.to_s)
else
raise "unsupported OS loader type"
end
@@ -362,7 +375,11 @@ EOF
end
def file_exist?(file)
- execute("test -e #{file}").success?
+ execute("test -e '#{file}'").success?
+ end
+
+ def directory_exist?(directory)
+ execute("test -d '#{directory}'").success?
end
def file_content(file, user = 'root')
@@ -374,6 +391,13 @@ EOF
return cmd.stdout
end
+ def file_append(file, line, user = 'root')
+ cmd = execute("echo '#{line}' >> '#{file}'", user)
+ assert(cmd.success?,
+ "Could not append to '#{file}':\n#{cmd.stdout}\n#{cmd.stderr}")
+ return cmd.stdout
+ end
+
def save_snapshot(path)
@domain.save(path)
@display.stop
@@ -381,9 +405,9 @@ EOF
def restore_snapshot(path)
# Clean up current domain so its snapshot can be restored
- clean_up_domain
- Libvirt::Domain::restore(@@virt, path)
- @domain = @@virt.lookup_domain_by_name(@domain_name)
+ destroy_and_undefine
+ Libvirt::Domain::restore(@virt, path)
+ @domain = @virt.lookup_domain_by_name(@domain_name)
@display.start
end
@@ -404,12 +428,6 @@ EOF
@display.stop
end
- def destroy
- clean_up_domain
- clean_up_net
- power_off
- end
-
def take_screenshot(description)
@display.take_screenshot(description)
end
diff --git a/features/support/hooks.rb b/features/support/hooks.rb
index 2f2f98c..8069ef9 100644
--- a/features/support/hooks.rb
+++ b/features/support/hooks.rb
@@ -14,42 +14,42 @@ rescue Errno::EACCES => e
end
def delete_all_snapshots
- Dir.glob("#{$tmp_dir}/*.state").each do |snapshot|
+ Dir.glob("#{$config["TMP_DIR"]}/*.state").each do |snapshot|
delete_snapshot(snapshot)
end
end
BeforeFeature('@product') do |feature|
- if File.exist?($tmp_dir)
- if !File.directory?($tmp_dir)
- raise "Temporary directory '#{$tmp_dir}' exists but is not a " +
+ if File.exist?($config["TMP_DIR"])
+ if !File.directory?($config["TMP_DIR"])
+ raise "Temporary directory '#{$config["TMP_DIR"]}' exists but is not a " +
"directory"
end
- if !File.owned?($tmp_dir)
- raise "Temporary directory '#{$tmp_dir}' must be owned by the " +
+ if !File.owned?($config["TMP_DIR"])
+ raise "Temporary directory '#{$config["TMP_DIR"]}' must be owned by the " +
"current user"
end
- FileUtils.chmod(0755, $tmp_dir)
+ FileUtils.chmod(0755, $config["TMP_DIR"])
else
begin
- Dir.mkdir($tmp_dir)
+ Dir.mkdir($config["TMP_DIR"])
rescue Errno::EACCES => e
raise "Cannot create temporary directory: #{e.to_s}"
end
end
- delete_all_snapshots if !$keep_snapshots
- if $tails_iso.nil?
+ delete_all_snapshots if !KEEP_SNAPSHOTS
+ if TAILS_ISO.nil?
raise "No Tails ISO image specified, and none could be found in the " +
"current directory"
end
- if File.exist?($tails_iso)
+ if File.exist?(TAILS_ISO)
# Workaround: when libvirt takes ownership of the ISO image it may
# become unreadable for the live user inside the guest in the
# host-to-guest share used for some tests.
- if !File.world_readable?($tails_iso)
- if File.owned?($tails_iso)
- File.chmod(0644, $tails_iso)
+ if !File.world_readable?(TAILS_ISO)
+ if File.owned?(TAILS_ISO)
+ File.chmod(0644, TAILS_ISO)
else
raise "warning: the Tails ISO image must be world readable or be " +
"owned by the current user to be available inside the guest " +
@@ -57,30 +57,35 @@ BeforeFeature('@product') do |feature|
end
end
else
- raise "The specified Tails ISO image '#{$tails_iso}' does not exist"
+ raise "The specified Tails ISO image '#{TAILS_ISO}' does not exist"
end
- puts "Testing ISO image: #{File.basename($tails_iso)}"
+ puts "Testing ISO image: #{File.basename(TAILS_ISO)}"
base = File.basename(feature.file, ".feature").to_s
- $background_snapshot = "#{$tmp_dir}/#{base}_background.state"
+ $background_snapshot = "#{$config["TMP_DIR"]}/#{base}_background.state"
+ $virt = Libvirt::open("qemu:///system")
+ $vmnet = VMNet.new($virt, VM_XML_PATH)
+ $vmstorage = VMStorage.new($virt, VM_XML_PATH)
end
AfterFeature('@product') do
- delete_snapshot($background_snapshot) if !$keep_snapshots
- VM.storage.clear_volumes if VM.storage
+ delete_snapshot($background_snapshot) if !KEEP_SNAPSHOTS
+ $vmstorage.clear_pool
+ $vmnet.destroy_and_undefine
+ $virt.close
end
BeforeFeature('@product', '@old_iso') do
- if $old_tails_iso.nil?
+ if OLD_TAILS_ISO.nil?
raise "No old Tails ISO image specified, and none could be found in the " +
"current directory"
end
- if !File.exist?($old_tails_iso)
- raise "The specified old Tails ISO image '#{$old_tails_iso}' does not exist"
+ if !File.exist?(OLD_TAILS_ISO)
+ raise "The specified old Tails ISO image '#{OLD_TAILS_ISO}' does not exist"
end
- if $tails_iso == $old_tails_iso
+ if TAILS_ISO == OLD_TAILS_ISO
raise "The old Tails ISO is the same as the Tails ISO we're testing"
end
- puts "Using old ISO image: #{File.basename($old_tails_iso)}"
+ puts "Using old ISO image: #{File.basename(OLD_TAILS_ISO)}"
end
# BeforeScenario
@@ -98,17 +103,17 @@ end
# AfterScenario
After('@product') do |scenario|
if (scenario.status != :passed)
- time_of_fail = Time.now - $time_at_start
+ time_of_fail = Time.now - TIME_AT_START
secs = "%02d" % (time_of_fail % 60)
mins = "%02d" % ((time_of_fail / 60) % 60)
hrs = "%02d" % (time_of_fail / (60*60))
STDERR.puts "Scenario failed at time #{hrs}:#{mins}:#{secs}"
base = File.basename(scenario.feature.file, ".feature").to_s
tmp = @screen.capture.getFilename
- out = "#{$tmp_dir}/#{base}-#{DateTime.now}.png"
+ out = "#{$config["TMP_DIR"]}/#{base}-#{DateTime.now}.png"
FileUtils.mv(tmp, out)
STDERR.puts("Took screenshot \"#{out}\"")
- if $pause_on_fail
+ if $config["PAUSE_ON_FAIL"]
STDERR.puts ""
STDERR.puts "Press ENTER to continue running the test suite"
STDIN.gets
@@ -118,11 +123,32 @@ After('@product') do |scenario|
@sniffer.stop
@sniffer.clear
end
- @vm.destroy if @vm
+ @vm.destroy_and_undefine if @vm
end
After('@product', '~@keep_volumes') do
- VM.storage.clear_volumes
+ $vmstorage.clear_volumes
+end
+
+Before('@product', '@check_tor_leaks') do |scenario|
+ feature_file_name = File.basename(scenario.feature.file, ".feature").to_s
+ @tor_leaks_sniffer = Sniffer.new(feature_file_name + "_sniffer", $vmnet)
+ @tor_leaks_sniffer.capture
+end
+
+After('@product', '@check_tor_leaks') do |scenario|
+ @tor_leaks_sniffer.stop
+ if (scenario.status == :passed)
+ if @bridge_hosts.nil?
+ expected_tor_nodes = get_all_tor_nodes
+ else
+ expected_tor_nodes = @bridge_hosts
+ end
+ leaks = FirewallLeakCheck.new(@tor_leaks_sniffer.pcap_file,
+ expected_tor_nodes)
+ leaks.assert_no_leaks
+ end
+ @tor_leaks_sniffer.clear
end
# For @source tests
@@ -151,6 +177,5 @@ BeforeFeature('@product', '@source') do |feature|
end
at_exit do
- delete_all_snapshots if !$keep_snapshots
- VM.storage.clear_pool if VM.storage
+ delete_all_snapshots if !KEEP_SNAPSHOTS
end
diff --git a/features/time_syncing.feature b/features/time_syncing.feature
index a32d5a7..1a9eab7 100644
--- a/features/time_syncing.feature
+++ b/features/time_syncing.feature
@@ -1,4 +1,4 @@
-@product
+@product @check_tor_leaks
Feature: Time syncing
As a Tails user
I want Tor to work properly
@@ -39,3 +39,53 @@ Feature: Time syncing
Then Tails clock is less than 5 minutes incorrect
# Scenario: Clock vs Tor consensus' valid-{after,until} etc.
+
+ Scenario: Create a new snapshot to the same state (w.r.t. Sikuli steps) as the Background except we're now in bridge mode
+ Given a computer
+ And the network is unplugged
+ And I start the computer
+ And the computer boots Tails
+ And I enable more Tails Greeter options
+ And I enable the specific Tor configuration option
+ And I log in to a new session
+ And GNOME has started
+ And I save the state so the background can be restored next scenario
+
+ Scenario: Clock with host's time in bridge mode
+ When the network is plugged
+ And the Tor Launcher autostarts
+ And I configure some Bridge pluggable transports in Tor Launcher
+ And Tor is ready
+ Then Tails clock is less than 5 minutes incorrect
+
+ Scenario: Clock is one day in the past in bridge mode
+ When I bump the system time with "-1 day"
+ And the network is plugged
+ And the Tor Launcher autostarts
+ And I configure some Bridge pluggable transports in Tor Launcher
+ And Tor is ready
+ Then Tails clock is less than 5 minutes incorrect
+
+ Scenario: Clock way in the past in bridge mode
+ When I set the system time to "01 Jan 2000 12:34:56"
+ And the network is plugged
+ And the Tor Launcher autostarts
+ And I configure some Bridge pluggable transports in Tor Launcher
+ And Tor is ready
+ Then Tails clock is less than 5 minutes incorrect
+
+ Scenario: Clock is one day in the future in bridge mode
+ When I bump the system time with "+1 day"
+ And the network is plugged
+ And the Tor Launcher autostarts
+ And I configure some Bridge pluggable transports in Tor Launcher
+ And Tor is ready
+ Then Tails clock is less than 5 minutes incorrect
+
+ Scenario: Clock way in the future in bridge mode
+ When I set the system time to "01 Jan 2020 12:34:56"
+ And the network is plugged
+ And the Tor Launcher autostarts
+ And I configure some Bridge pluggable transports in Tor Launcher
+ And Tor is ready
+ Then Tails clock is less than 5 minutes incorrect
diff --git a/features/tor_bridges.feature b/features/tor_bridges.feature
new file mode 100644
index 0000000..677d067
--- /dev/null
+++ b/features/tor_bridges.feature
@@ -0,0 +1,52 @@
+@product
+Feature: Using Tails with Tor pluggable transports
+ As a Tails user
+ I want to circumvent censorship of Tor by using Tor pluggable transports
+ And avoid connecting directly to the Tor Network
+
+ Background:
+ Given a computer
+ And the network is unplugged
+ And I start the computer
+ And the computer boots Tails
+ And I enable more Tails Greeter options
+ And I enable the specific Tor configuration option
+ And I log in to a new session
+ And GNOME has started
+ And I save the state so the background can be restored next scenario
+
+ Scenario: Using bridges
+ Given I capture all network traffic
+ When the network is plugged
+ And the Tor Launcher autostarts
+ And I configure some Bridge pluggable transports in Tor Launcher
+ Then Tor is ready
+ And available upgrades have been checked
+ And all Internet traffic has only flowed through the configured pluggable transports
+
+ Scenario: Using obfs2 pluggable transports
+ Given I capture all network traffic
+ When the network is plugged
+ And the Tor Launcher autostarts
+ And I configure some obfs2 pluggable transports in Tor Launcher
+ Then Tor is ready
+ And available upgrades have been checked
+ And all Internet traffic has only flowed through the configured pluggable transports
+
+ Scenario: Using obfs3 pluggable transports
+ Given I capture all network traffic
+ When the network is plugged
+ And the Tor Launcher autostarts
+ And I configure some obfs3 pluggable transports in Tor Launcher
+ Then Tor is ready
+ And available upgrades have been checked
+ And all Internet traffic has only flowed through the configured pluggable transports
+
+ Scenario: Using obfs4 pluggable transports
+ Given I capture all network traffic
+ When the network is plugged
+ And the Tor Launcher autostarts
+ And I configure some obfs4 pluggable transports in Tor Launcher
+ Then Tor is ready
+ And available upgrades have been checked
+ And all Internet traffic has only flowed through the configured pluggable transports
diff --git a/features/tor_enforcement.feature b/features/tor_enforcement.feature
new file mode 100644
index 0000000..bdfd285
--- /dev/null
+++ b/features/tor_enforcement.feature
@@ -0,0 +1,72 @@
+@product
+Feature: The Tor enforcement is effective
+ As a Tails user
+ I want all direct Internet connections I do by mistake or applications do by misconfiguration or buggy leaks to be blocked
+ And as a Tails developer
+ I want to ensure that the automated test suite detects firewall leaks reliably
+
+ Background:
+ Given a computer
+ When I start Tails from DVD and I login
+ And I save the state so the background can be restored next scenario
+
+ Scenario: The firewall configuration is very restrictive
+ Then the firewall's policy is to drop all IPv4 traffic
+ And the firewall is configured to only allow the clearnet and debian-tor users to connect directly to the Internet over IPv4
+ And the firewall's NAT rules only redirect traffic for Tor's TransPort and DNSPort
+ And the firewall is configured to block all IPv6 traffic
+
+ Scenario: Anti test: Detecting IPv4 TCP leaks from the Unsafe Browser with the firewall leak detector
+ Given I capture all network traffic
+ When I successfully start the Unsafe Browser
+ And I open the address "https://check.torproject.org" in the Unsafe Browser
+ And I see "UnsafeBrowserTorCheckFail.png" after at most 60 seconds
+ Then the firewall leak detector has detected IPv4 TCP leaks
+
+ Scenario: Anti test: Detecting IPv4 TCP leaks of TCP DNS lookups with the firewall leak detector
+ Given I capture all network traffic
+ And I disable Tails' firewall
+ When I do a TCP DNS lookup of "torproject.org"
+ Then the firewall leak detector has detected IPv4 TCP leaks
+
+ Scenario: Anti test: Detecting IPv4 non-TCP leaks (UDP) of UDP DNS lookups with the firewall leak detector
+ Given I capture all network traffic
+ And I disable Tails' firewall
+ When I do a UDP DNS lookup of "torproject.org"
+ Then the firewall leak detector has detected IPv4 non-TCP leaks
+
+ Scenario: Anti test: Detecting IPv4 non-TCP (ICMP) leaks of ping with the firewall leak detector
+ Given I capture all network traffic
+ And I disable Tails' firewall
+ When I send some ICMP pings
+ Then the firewall leak detector has detected IPv4 non-TCP leaks
+
+ @check_tor_leaks
+ Scenario: The Tor enforcement is effective at blocking untorified TCP connection attempts
+ When I open an untorified TCP connections to 1.2.3.4 on port 42 that is expected to fail
+ Then the untorified connection fails
+ And the untorified connection is logged as dropped by the firewall
+
+ @check_tor_leaks
+ Scenario: The Tor enforcement is effective at blocking untorified UDP connection attempts
+ When I open an untorified UDP connections to 1.2.3.4 on port 42 that is expected to fail
+ Then the untorified connection fails
+ And the untorified connection is logged as dropped by the firewall
+
+ @check_tor_leaks
+ Scenario: The Tor enforcement is effective at blocking untorified ICMP connection attempts
+ When I open an untorified ICMP connections to 1.2.3.4 that is expected to fail
+ Then the untorified connection fails
+ And the untorified connection is logged as dropped by the firewall
+
+ Scenario: The system DNS is always set up to use Tor's DNSPort
+ Given a computer
+ And the network is unplugged
+ And I start the computer
+ And the computer boots Tails
+ And I log in to a new session
+ And GNOME has started
+ And the system DNS is using the local DNS resolver
+ And the network is plugged
+ And Tor is ready
+ Then the system DNS is still using the local DNS resolver
diff --git a/features/tor_stream_isolation.feature b/features/tor_stream_isolation.feature
new file mode 100644
index 0000000..345888a
--- /dev/null
+++ b/features/tor_stream_isolation.feature
@@ -0,0 +1,60 @@
+@product @check_tor_leaks
+Feature: Tor stream isolation is effective
+ As a Tails user
+ I want my Torified sessions to be sensibly isolated from each other to prevent identity correlation
+
+ Background:
+ Given a computer
+ When I start Tails from DVD and I login
+ And I save the state so the background can be restored next scenario
+
+ Scenario: tails-security-check is using the Tails-specific SocksPort
+ When I monitor the network connections of tails-security-check
+ And I re-run tails-security-check
+ Then I see that tails-security-check is properly stream isolated
+
+ Scenario: htpdate is using the Tails-specific SocksPort
+ When I monitor the network connections of htpdate
+ And I re-run htpdate
+ Then I see that htpdate is properly stream isolated
+
+ Scenario: tails-upgrade-frontend-wrapper is using the Tails-specific SocksPort
+ When I monitor the network connections of tails-upgrade-frontend-wrapper
+ And I re-run tails-upgrade-frontend-wrapper
+ Then I see that tails-upgrade-frontend-wrapper is properly stream isolated
+
+ Scenario: The Tor Browser is using the web browser-specific SocksPort
+ When I monitor the network connections of Tor Browser
+ And I start the Tor Browser
+ And the Tor Browser has started and loaded the startup page
+ Then I see that Tor Browser is properly stream isolated
+
+ Scenario: Gobby is using the default SocksPort
+ When I monitor the network connections of Gobby
+ And I start "Gobby" via the GNOME "Internet" applications menu
+ And I connect Gobby to "gobby.debian.org"
+ Then I see that Gobby is properly stream isolated
+
+ Scenario: SSH is using the default SocksPort
+ When I monitor the network connections of SSH
+ And I run "ssh lizard.tails.boum.org" in GNOME Terminal
+ And I see "SSHAuthVerification.png" after at most 60 seconds
+ Then I see that SSH is properly stream isolated
+
+ Scenario: whois lookups use the default SocksPort
+ When I monitor the network connections of whois
+ And I query the whois directory service for "boum.org"
+ And the whois command is successful
+ Then I see that whois is properly stream isolated
+
+ Scenario: Explicitly torify-wrapped applications are using the default SocksPort
+ When I monitor the network connections of Gobby
+ And I run "torify /usr/bin/gobby-0.5" in GNOME Terminal
+ And I connect Gobby to "gobby.debian.org"
+ Then I see that Gobby is properly stream isolated
+
+ Scenario: Explicitly torsocks-wrapped applications are using the default SocksPort
+ When I monitor the network connections of Gobby
+ And I run "torsocks /usr/bin/gobby-0.5" in GNOME Terminal
+ And I connect Gobby to "gobby.debian.org"
+ Then I see that Gobby is properly stream isolated
diff --git a/features/torified_browsing.feature b/features/torified_browsing.feature
index cc9ddf1..fd6fe25 100644
--- a/features/torified_browsing.feature
+++ b/features/torified_browsing.feature
@@ -6,7 +6,6 @@ Feature: Browsing the web using the Tor Browser
Background:
Given a computer
- And I capture all network traffic
And I start the computer
And the computer boots Tails
And I log in to a new session
@@ -16,20 +15,71 @@ Feature: Browsing the web using the Tor Browser
And all notifications have disappeared
And I save the state so the background can be restored next scenario
+ Scenario: The Tor Browser directory is usable
+ Then the amnesiac Tor Browser directory exists
+ And there is a GNOME bookmark for the amnesiac Tor Browser directory
+ And the persistent Tor Browser directory does not exist
+ When I start the Tor Browser
+ And the Tor Browser has started and loaded the startup page
+ Then I can save the current page as "index.html" to the default downloads directory
+ And I can print the current page as "output.pdf" to the default downloads directory
+
+ @check_tor_leaks
+ Scenario: Importing an OpenPGP key from a website
+ When I start the Tor Browser
+ And the Tor Browser has started and loaded the startup page
+ And I open the address "https://tails.boum.org/tails-signing.key" in the Tor Browser
+ Then I see "OpenWithImportKey.png" after at most 20 seconds
+ When I accept to import the key with Seahorse
+ Then I see "KeyImportedNotification.png" after at most 10 seconds
+
+ @check_tor_leaks
+ Scenario: Playing HTML5 audio
+ When I start the Tor Browser
+ And the Tor Browser has started and loaded the startup page
+ And no application is playing audio
+ And I open the address "http://www.terrillthompson.com/tests/html5-audio.html" in the Tor Browser
+ And I click the HTML5 play button
+ And 1 application is playing audio after 10 seconds
+
+ @check_tor_leaks
+ Scenario: Watching a WebM video
+ When I start the Tor Browser
+ And the Tor Browser has started and loaded the startup page
+ And I open the address "https://webm.html5.org/test.webm" in the Tor Browser
+ And I click the blocked video icon
+ And I see "TorBrowserNoScriptTemporarilyAllowDialog.png" after at most 10 seconds
+ And I accept to temporarily allow playing this video
+ Then I see "TorBrowserSampleRemoteWebMVideoFrame.png" after at most 180 seconds
+
+ Scenario: I can view a file stored in "~/Tor Browser" but not in ~/.gnupg
+ Given I copy "/usr/share/synaptic/html/index.html" to "/home/amnesia/Tor Browser/synaptic.html" as user "amnesia"
+ And I copy "/usr/share/synaptic/html/index.html" to "/home/amnesia/.gnupg/synaptic.html" as user "amnesia"
+ And I start the Tor Browser
+ And the Tor Browser has started and loaded the startup page
+ When I open the address "file:///home/amnesia/Tor Browser/synaptic.html" in the Tor Browser
+ Then I see "TorBrowserSynapticManual.png" after at most 10 seconds
+ When I open the address "file:///home/amnesia/.gnupg/synaptic.html" in the Tor Browser
+ Then I see "TorBrowserUnableToOpen.png" after at most 10 seconds
+
+ Scenario: The "Tails documentation" link on the Desktop works
+ When I double-click on the "Tails documentation" link on the Desktop
+ Then the Tor Browser has started
+ And I see "TailsOfflineDocHomepage.png" after at most 10 seconds
+
Scenario: The Tor Browser uses TBB's shared libraries
When I start the Tor Browser
And the Tor Browser has started
Then the Tor Browser uses all expected TBB shared libraries
+ @check_tor_leaks
Scenario: Opening check.torproject.org in the Tor Browser shows the green onion and the congratulations message
When I start the Tor Browser
And the Tor Browser has started and loaded the startup page
And I open the address "https://check.torproject.org" in the Tor Browser
Then I see "TorBrowserTorCheck.png" after at most 180 seconds
- And all Internet traffic has only flowed through Tor
Scenario: The Tor Browser should not have any plugins enabled
When I start the Tor Browser
And the Tor Browser has started and loaded the startup page
- And I open the address "about:plugins" in the Tor Browser
- Then I see "TorBrowserNoPlugins.png" after at most 60 seconds
+ Then the Tor Browser has no plugins installed
diff --git a/features/torified_git.feature b/features/torified_git.feature
new file mode 100644
index 0000000..4aa5132
--- /dev/null
+++ b/features/torified_git.feature
@@ -0,0 +1,36 @@
+@product @check_tor_leaks
+Feature: Cloning a Git repository
+ As a Tails user
+ when I clone a Git repository
+ all Internet traffic should flow only through Tor
+
+ Background:
+ Given a computer
+ And I start the computer
+ And the computer boots Tails
+ And I log in to a new session
+ And GNOME has started
+ And Tor is ready
+ And available upgrades have been checked
+ And all notifications have disappeared
+ And I save the state so the background can be restored next scenario
+
+ Scenario: Cloning a Git repository anonymously over HTTPS
+ When I run "git clone https://git-tails.immerda.ch/myprivatekeyispublic/testing" in GNOME Terminal
+ Then process "git" is running within 10 seconds
+ And process "git" has stopped running after at most 180 seconds
+ And the Git repository "testing" has been cloned successfully
+
+ Scenario: Cloning a Git repository anonymously over the Git protocol
+ When I run "git clone git://git.tails.boum.org/myprivatekeyispublic/testing" in GNOME Terminal
+ Then process "git" is running within 10 seconds
+ And process "git" has stopped running after at most 180 seconds
+ And the Git repository "testing" has been cloned successfully
+
+ Scenario: Cloning git repository over SSH
+ Given I have the SSH key pair for a Git repository
+ When I run "git clone tails@git.tails.boum.org:myprivatekeyispublic/testing" in GNOME Terminal
+ Then process "git" is running within 10 seconds
+ When I verify the SSH fingerprint for the Git repository
+ And process "git" has stopped running after at most 180 seconds
+ Then the Git repository "testing" has been cloned successfully
diff --git a/features/torified_gnupg.feature b/features/torified_gnupg.feature
index a582068..e5246c5 100644
--- a/features/torified_gnupg.feature
+++ b/features/torified_gnupg.feature
@@ -1,4 +1,4 @@
-@product
+@product @check_tor_leaks
Feature: Keyserver interaction with GnuPG
As a Tails user
when I interact with keyservers using various GnuPG tools
@@ -7,7 +7,6 @@ Feature: Keyserver interaction with GnuPG
Background:
Given a computer
- And I capture all network traffic
And I start the computer
And the computer boots Tails
And I log in to a new session
@@ -23,9 +22,7 @@ Feature: Keyserver interaction with GnuPG
Then GnuPG uses the configured keyserver
And the GnuPG fetch is successful
And the "10CC5BC7" key is in the live user's public keyring after at most 120 seconds
- And all Internet traffic has only flowed through Tor
Scenario: Fetching OpenPGP keys using Seahorse should work and be done over Tor.
When I fetch the "10CC5BC7" OpenPGP key using Seahorse
Then the "10CC5BC7" key is in the live user's public keyring after at most 120 seconds
- And all Internet traffic has only flowed through Tor
diff --git a/features/torified_misc.feature b/features/torified_misc.feature
new file mode 100644
index 0000000..7ce111a
--- /dev/null
+++ b/features/torified_misc.feature
@@ -0,0 +1,32 @@
+@product @check_tor_leaks
+Feature: Various checks for torified software
+
+ Background:
+ Given a computer
+ And I start the computer
+ And the computer boots Tails
+ And I log in to a new session
+ And GNOME has started
+ And Tor is ready
+ And all notifications have disappeared
+ And available upgrades have been checked
+ And I save the state so the background can be restored next scenario
+
+ Scenario: wget(1) should work for HTTP and go through Tor.
+ When I wget "http://example.com/" to stdout
+ Then the wget command is successful
+ And the wget standard output contains "Example Domain"
+
+ Scenario: wget(1) should work for HTTPS and go through Tor.
+ When I wget "https://example.com/" to stdout
+ Then the wget command is successful
+ And the wget standard output contains "Example Domain"
+
+ Scenario: wget(1) with tricky options should work for HTTP and go through Tor.
+ When I wget "http://195.154.14.189/tails/stable/" to stdout with the '--spider --header="Host: dl.amnesia.boum.org"' options
+ Then the wget command is successful
+
+ Scenario: whois(1) should work and go through Tor.
+ When I query the whois directory service for "torproject.org"
+ Then the whois command is successful
+ Then the whois standard output contains "The Tor Project"
diff --git a/features/totem.feature b/features/totem.feature
index 2729b27..b5288d8 100644
--- a/features/totem.feature
+++ b/features/totem.feature
@@ -24,9 +24,9 @@ Feature: Using Totem
When I try to open "/home/amnesia/.gnupg/video.mp4" with Totem
Then I see "TotemUnableToOpen.png" after at most 10 seconds
+ @check_tor_leaks
Scenario: Watching a WebM video over HTTPS, with and without the command-line
Given a computer
- And I capture all network traffic
And I start Tails from DVD and I login
When I open "https://webm.html5.org/test.webm" with Totem
Then I see "SampleRemoteWebMVideoFrame.png" after at most 10 seconds
@@ -34,7 +34,6 @@ Feature: Using Totem
And I start Totem through the GNOME menu
When I load the "https://webm.html5.org/test.webm" URL in Totem
Then I see "SampleRemoteWebMVideoFrame.png" after at most 10 seconds
- And all Internet traffic has only flowed through Tor
@keep_volumes
Scenario: Installing Tails on a USB drive, creating a persistent partition, copying video files to it
diff --git a/features/unsafe_browser.feature b/features/unsafe_browser.feature
index 80d91af..dd36f35 100644
--- a/features/unsafe_browser.feature
+++ b/features/unsafe_browser.feature
@@ -17,31 +17,105 @@ Feature: Browsing the web using the Unsafe Browser
Scenario: Starting the Unsafe Browser works as it should.
When I successfully start the Unsafe Browser
- Then the Unsafe Browser has a red theme
+ Then the Unsafe Browser runs as the expected user
+ And the Unsafe Browser has a red theme
And the Unsafe Browser shows a warning as its start page
+ And the Unsafe Browser has no plugins installed
+ And the Unsafe Browser has no add-ons installed
+ And the Unsafe Browser has only Firefox's default bookmarks configured
+ And the Unsafe Browser has no proxy configured
And the Unsafe Browser uses all expected TBB shared libraries
- Scenario: Closing the Unsafe Browser shows a stop notification.
+ Scenario: Starting the Unsafe Browser works in Arabic
+ When I start the Unsafe Browser in the "ar_EG" locale
+ Then the Unsafe Browser has started
+
+ Scenario: Starting the Unsafe Browser works in Brazilian Portuguese
+ When I start the Unsafe Browser in the "pt_BR" locale
+ Then the Unsafe Browser has started
+
+ Scenario: Starting the Unsafe Browser works in Chinese
+ When I start the Unsafe Browser in the "zh_CN" locale
+ Then the Unsafe Browser has started
+
+ Scenario: Starting the Unsafe Browser works in Dutch
+ When I start the Unsafe Browser in the "nl_NL" locale
+ Then the Unsafe Browser has started
+
+ Scenario: Starting the Unsafe Browser works in Farsi
+ When I start the Unsafe Browser in the "fa_IR" locale
+ Then the Unsafe Browser has started
+
+ Scenario: Starting the Unsafe Browser works in French
+ When I start the Unsafe Browser in the "fr_FR" locale
+ Then the Unsafe Browser has started
+
+ Scenario: Starting the Unsafe Browser works in German
+ When I start the Unsafe Browser in the "de_DE" locale
+ Then the Unsafe Browser has started
+
+ Scenario: Starting the Unsafe Browser works in Italian
+ When I start the Unsafe Browser in the "it_IT" locale
+ Then the Unsafe Browser has started
+
+ Scenario: Starting the Unsafe Browser works in Korean
+ When I start the Unsafe Browser in the "kr_KR" locale
+ Then the Unsafe Browser has started
+
+ Scenario: Starting the Unsafe Browser works in Polish
+ When I start the Unsafe Browser in the "pl_PL" locale
+ Then the Unsafe Browser has started
+
+ Scenario: Starting the Unsafe Browser works in Portuguese
+ When I start the Unsafe Browser in the "pt_PT" locale
+ Then the Unsafe Browser has started
+
+ Scenario: Starting the Unsafe Browser works in Russian
+ When I start the Unsafe Browser in the "ru_RU" locale
+ Then the Unsafe Browser has started
+
+ Scenario: Starting the Unsafe Browser works in Turkish
+ When I start the Unsafe Browser in the "tr_TR" locale
+ Then the Unsafe Browser has started
+
+ Scenario: Starting the Unsafe Browser works in Vietnamese
+ When I start the Unsafe Browser in the "vi_VN" locale
+ Then the Unsafe Browser has started
+
+ Scenario: Closing the Unsafe Browser shows a stop notification and properly tears down the chroot.
When I successfully start the Unsafe Browser
And I close the Unsafe Browser
Then I see the Unsafe Browser stop notification
+ And the Unsafe Browser chroot is torn down
Scenario: Starting a second instance of the Unsafe Browser results in an error message being shown.
When I successfully start the Unsafe Browser
And I start the Unsafe Browser
Then I see a warning about another instance already running
- Scenario: The Unsafe Browser cannot be restarted before the previous instance has been cleaned up.
- When I successfully start the Unsafe Browser
- And I close the Unsafe Browser
- And I start the Unsafe Browser
- Then I see a warning about another instance already running
-
Scenario: Opening check.torproject.org in the Unsafe Browser shows the red onion and a warning message.
When I successfully start the Unsafe Browser
And I open the address "https://check.torproject.org" in the Unsafe Browser
Then I see "UnsafeBrowserTorCheckFail.png" after at most 60 seconds
+ And the clearnet user has sent packets out to the Internet
Scenario: The Unsafe Browser cannot be configured to use Tor and other local proxies.
When I successfully start the Unsafe Browser
Then I cannot configure the Unsafe Browser to use any local proxies
+
+ Scenario: The Unsafe Browser will not make any connections to the Internet which are not user initiated
+ Given I capture all network traffic
+ And Tor is ready
+ And I configure the Unsafe Browser to check for updates more frequently
+ But checking for updates is disabled in the Unsafe Browser's configuration
+ When I successfully start the Unsafe Browser
+ Then the Unsafe Browser has started
+ And I wait between 60 and 120 seconds
+ And the clearnet user has not sent packets out to the Internet
+ And all Internet traffic has only flowed through Tor
+
+ Scenario: Starting the Unsafe Browser without a network connection results in a complaint about no DNS server being configured
+ Given a computer
+ And I start Tails from DVD with network unplugged and I login
+ When I start the Unsafe Browser
+ Then the Unsafe Browser complains that no DNS server is configured
diff --git a/features/untrusted_partitions.feature b/features/untrusted_partitions.feature
index 816fbe7..1985896 100644
--- a/features/untrusted_partitions.feature
+++ b/features/untrusted_partitions.feature
@@ -3,15 +3,53 @@ Feature: Untrusted partitions
As a Tails user
I don't want to touch other media than the one Tails runs from
+ Scenario: Tails will not enable disk swap
+ Given a computer
+ And I create a 100 MiB disk named "swap"
+ And I create a gpt swap partition on disk "swap"
+ And I plug ide drive "swap"
+ When I start Tails with network unplugged and I login
+ Then a "linux-swap(v1)" partition was detected by Tails on drive "swap"
+ But Tails has no disk swap enabled
+
+ @keep_volumes
+ Scenario: Tails will detect LUKS-encrypted GPT partitions labeled "TailsData" stored on USB drives as persistence volumes when the removable flag is set
+ Given a computer
+ And I create a 100 MiB disk named "fake_TailsData"
+ And I create a gpt partition labeled "TailsData" with an ext4 filesystem encrypted with password "asdf" on disk "fake_TailsData"
+ And I plug removable usb drive "fake_TailsData"
+ When I start the computer
+ And the computer boots Tails
+ Then drive "fake_TailsData" is detected by Tails
+ And Tails Greeter has detected a persistence partition
+
+ @keep_volumes
+ Scenario: Tails will not detect LUKS-encrypted GPT partitions labeled "TailsData" stored on USB drives as persistence volumes when the removable flag is unset
+ Given a computer
+ And I plug non-removable usb drive "fake_TailsData"
+ When I start the computer
+ And the computer boots Tails
+ Then drive "fake_TailsData" is detected by Tails
+ And Tails Greeter has not detected a persistence partition
+
+ Scenario: Tails will not detect LUKS-encrypted GPT partitions labeled "TailsData" stored on local hard drives as persistence volumes
+ Given a computer
+ And I plug ide drive "fake_TailsData"
+ When I start the computer
+ And the computer boots Tails
+ Then drive "fake_TailsData" is detected by Tails
+ And Tails Greeter has not detected a persistence partition
+
@keep_volumes
Scenario: Tails can boot from live systems stored on hard drives
Given a computer
And I create a 2 GiB disk named "live_hd"
- And I cat an ISO hybrid of the Tails image to disk "live_hd"
+ And I cat an ISO of the Tails image to disk "live_hd"
And the computer is set to boot from ide drive "live_hd"
And I set Tails to boot with options "live-media="
- And I start Tails from DVD with network unplugged and I login
- Then Tails seems to have booted normally
+ When I start Tails with network unplugged and I login
+ Then Tails is running from ide drive "live_hd"
+ And Tails seems to have booted normally
Scenario: Tails booting from a DVD does not use live systems stored on hard drives
Given a computer
@@ -23,8 +61,7 @@ Feature: Untrusted partitions
Scenario: Booting Tails does not automount untrusted ext2 partitions
Given a computer
And I create a 100 MiB disk named "gpt_ext2"
- And I create a gpt label on disk "gpt_ext2"
- And I create a ext2 filesystem on disk "gpt_ext2"
+ And I create a gpt partition with an ext2 filesystem on disk "gpt_ext2"
And I plug ide drive "gpt_ext2"
And I start Tails from DVD with network unplugged and I login
Then drive "gpt_ext2" is detected by Tails
@@ -33,8 +70,7 @@ Feature: Untrusted partitions
Scenario: Booting Tails does not automount untrusted fat32 partitions
Given a computer
And I create a 100 MiB disk named "msdos_fat32"
- And I create a msdos label on disk "msdos_fat32"
- And I create a fat32 filesystem on disk "msdos_fat32"
+ And I create an msdos partition with a vfat filesystem on disk "msdos_fat32"
And I plug ide drive "msdos_fat32"
And I start Tails from DVD with network unplugged and I login
Then drive "msdos_fat32" is detected by Tails
diff --git a/features/usb_install.feature b/features/usb_install.feature
index b40ca93..aadc6e9 100644
--- a/features/usb_install.feature
+++ b/features/usb_install.feature
@@ -9,7 +9,7 @@ Feature: Installing Tails to a USB drive, upgrading it, and using persistence
Scenario: Installing Tails to a pristine USB drive
Given a computer
And I start Tails from DVD with network unplugged and I login
- And I create a new 4 GiB USB drive named "current"
+ And I create a 4 GiB disk named "current"
And I plug USB drive "current"
And I "Clone & Install" Tails to USB drive "current"
Then the running Tails is installed on USB drive "current"
@@ -34,6 +34,7 @@ Feature: Installing Tails to a USB drive, upgrading it, and using persistence
And Tails is running from USB drive "current"
And the boot device has safe access rights
And there is no persistence partition on USB drive "current"
+ And the persistent Tor Browser directory does not exist
And I create a persistent partition with password "asdf"
Then a Tails persistence partition with password "asdf" exists on USB drive "current"
And I shutdown Tails and wait for the computer to power off
@@ -48,6 +49,23 @@ Feature: Installing Tails to a USB drive, upgrading it, and using persistence
But a Tails persistence partition with password "asdf" exists on USB drive "current"
@keep_volumes
+ Scenario: The persistent Tor Browser directory is usable
+ Given a computer
+ And I start Tails from USB drive "current" and I login with persistence password "asdf"
+ And Tails is running from USB drive "current"
+ And Tor is ready
+ And available upgrades have been checked
+ And all notifications have disappeared
+ Then the persistent Tor Browser directory exists
+ And there is a GNOME bookmark for the persistent Tor Browser directory
+ When I start the Tor Browser
+ And the Tor Browser has started and loaded the startup page
+ And I can save the current page as "index.html" to the persistent Tor Browser directory
+ When I open the address "file:///home/amnesia/Persistent/Tor Browser/index.html" in the Tor Browser
+ Then I see "TorBrowserSavedStartupPage.png" after at most 10 seconds
+ And I can print the current page as "output.pdf" to the persistent Tor Browser directory
+
+ @keep_volumes
Scenario: Persistent browser bookmarks
Given a computer
And the computer is set to boot from USB drive "current"
@@ -60,10 +78,10 @@ Feature: Installing Tails to a USB drive, upgrading it, and using persistence
And I log in to a new session
And GNOME has started
And all notifications have disappeared
- And persistence is enabled
- And persistent filesystems have safe access rights
- And persistence configuration files have safe access rights
- And persistent directories have safe access rights
+ And all persistence presets are enabled
+ And all persistent filesystems have safe access rights
+ And all persistence configuration files have safe access rights
+ And all persistent directories have safe access rights
And I start the Tor Browser in offline mode
And the Tor Browser has started in offline mode
And I add a bookmark to eff.org in the Tor Browser
@@ -82,13 +100,14 @@ Feature: Installing Tails to a USB drive, upgrading it, and using persistence
And I start Tails from USB drive "current" with network unplugged and I login with persistence password "asdf"
Then Tails is running from USB drive "current"
And the boot device has safe access rights
- And persistence is enabled
+ And all persistence presets are enabled
And I write some files expected to persist
- And persistent filesystems have safe access rights
- And persistence configuration files have safe access rights
- And persistent directories have safe access rights
+ And all persistent filesystems have safe access rights
+ And all persistence configuration files have safe access rights
+ And all persistent directories have safe access rights
+ And I take note of which persistence presets are available
And I shutdown Tails and wait for the computer to power off
- Then only the expected files should persist on USB drive "current"
+ Then only the expected files are present on the persistence partition encrypted with password "asdf" on USB drive "current"
@keep_volumes
Scenario: Writing files to a read-only-enabled persistent partition
@@ -96,11 +115,13 @@ Feature: Installing Tails to a USB drive, upgrading it, and using persistence
And I start Tails from USB drive "current" with network unplugged and I login with read-only persistence password "asdf"
Then Tails is running from USB drive "current"
And the boot device has safe access rights
- And persistence is enabled
+ And all persistence presets are enabled
+ And there is no GNOME bookmark for the persistent Tor Browser directory
And I write some files not expected to persist
And I remove some files expected to persist
+ And I take note of which persistence presets are available
And I shutdown Tails and wait for the computer to power off
- Then only the expected files should persist on USB drive "current"
+ Then only the expected files are present on the persistence partition encrypted with password "asdf" on USB drive "current"
@keep_volumes
Scenario: Deleting a Tails persistent partition
@@ -124,7 +145,7 @@ Feature: Installing Tails to a USB drive, upgrading it, and using persistence
And I log in to a new session
And GNOME has started
And all notifications have disappeared
- And I create a new 4 GiB USB drive named "old"
+ And I create a 4 GiB disk named "old"
And I plug USB drive "old"
And I "Clone & Install" Tails to USB drive "old"
Then the running Tails is installed on USB drive "old"
@@ -137,6 +158,7 @@ Feature: Installing Tails to a USB drive, upgrading it, and using persistence
And I start Tails from USB drive "old" with network unplugged and I login
Then Tails is running from USB drive "old"
And I create a persistent partition with password "asdf"
+ And I take note of which persistence presets are available
Then a Tails persistence partition with password "asdf" exists on USB drive "old"
And I shutdown Tails and wait for the computer to power off
@@ -145,13 +167,14 @@ Feature: Installing Tails to a USB drive, upgrading it, and using persistence
Given a computer
And I start Tails from USB drive "old" with network unplugged and I login with persistence password "asdf"
Then Tails is running from USB drive "old"
- And persistence is enabled
+ And all persistence presets are enabled
And I write some files expected to persist
- And persistent filesystems have safe access rights
- And persistence configuration files have safe access rights
- And persistent directories have safe access rights
+ And all persistent filesystems have safe access rights
+ And all persistence configuration files have safe access rights
+ And all persistent directories from the old Tails version have safe access rights
+ And I take note of which persistence presets are available
And I shutdown Tails and wait for the computer to power off
- Then only the expected files should persist on USB drive "old"
+ Then only the expected files are present on the persistence partition encrypted with password "asdf" on USB drive "old"
@keep_volumes
Scenario: Upgrading an old Tails USB installation from a Tails DVD
@@ -167,10 +190,11 @@ Feature: Installing Tails to a USB drive, upgrading it, and using persistence
Scenario: Booting Tails from a USB drive upgraded from DVD with persistence enabled
Given a computer
And I start Tails from USB drive "to_upgrade" with network unplugged and I login with persistence password "asdf"
+ Then all persistence presets from the old Tails version are enabled
Then Tails is running from USB drive "to_upgrade"
And the boot device has safe access rights
- And the expected persistent files are present in the filesystem
- And persistent directories have safe access rights
+ And the expected persistent files created with the old Tails version are present in the filesystem
+ And all persistent directories from the old Tails version have safe access rights
@keep_volumes
Scenario: Upgrading an old Tails USB installation from another Tails USB drive
@@ -189,11 +213,11 @@ Feature: Installing Tails to a USB drive, upgrading it, and using persistence
Scenario: Booting Tails from a USB drive upgraded from USB with persistence enabled
Given a computer
And I start Tails from USB drive "to_upgrade" with network unplugged and I login with persistence password "asdf"
- Then persistence is enabled
+ Then all persistence presets from the old Tails version are enabled
And Tails is running from USB drive "to_upgrade"
And the boot device has safe access rights
- And the expected persistent files are present in the filesystem
- And persistent directories have safe access rights
+ And the expected persistent files created with the old Tails version are present in the filesystem
+ And all persistent directories from the old Tails version have safe access rights
@keep_volumes
Scenario: Upgrading an old Tails USB installation from an ISO image, running on the old version
@@ -220,11 +244,11 @@ Feature: Installing Tails to a USB drive, upgrading it, and using persistence
Scenario: Booting a USB drive upgraded from ISO with persistence enabled
Given a computer
And I start Tails from USB drive "to_upgrade" with network unplugged and I login with persistence password "asdf"
- Then persistence is enabled
+ Then all persistence presets from the old Tails version are enabled
And Tails is running from USB drive "to_upgrade"
And the boot device has safe access rights
- And the expected persistent files are present in the filesystem
- And persistent directories have safe access rights
+ And the expected persistent files created with the old Tails version are present in the filesystem
+ And all persistent directories from the old Tails version have safe access rights
@keep_volumes
Scenario: Installing Tails to a USB drive with an MBR partition table but no partitions
@@ -249,7 +273,7 @@ Feature: Installing Tails to a USB drive, upgrading it, and using persistence
Scenario: Cat:ing a Tails isohybrid to a USB drive and booting it
Given a computer
And I create a 4 GiB disk named "isohybrid"
- And I cat an ISO hybrid of the Tails image to disk "isohybrid"
+ And I cat an ISO of the Tails image to disk "isohybrid"
And I start Tails from USB drive "isohybrid" with network unplugged and I login
Then Tails is running from USB drive "isohybrid"