Repository: freedomofpress/securedrop

Issue:
Prompt users to fix upgrade issue for Tails 4.14 - 4.18 (or fix it for them?)
## Description

Tails automatic upgrades were broken between versions 4.14 - 4.18 inclusive ([Tails announcement](https://tails.boum.org/doc/upgrade/error/check/index.en.html#4.18)), and some users on older versions of Tails may not realize that they are missing auto-updates, and that there is a manual step required to fix them. 

We could:
- Make no code changes, and continue to use support messaging channels etc to remind folks of this issue
- Add text to the SecureDrop updater wizard, prompting users to update if their version of Tails is too old, or
- Perform the steps to fix automatic Tails updates ourselves, which (according to the above link) consist of 
```
torsocks curl --silent https://tails.boum.org/isrg-root-x1-cross-signed.pem \
| sudo tee --append /usr/local/etc/ssl/certs/tails.boum.org-CA.pem \
&& systemctl --user restart tails-upgrade-frontend
```  

I'm kind of in favour of the last option, and I can put in a PR for a check at the end of `securedrop_init` right before our GUI updater runs. What do others think?  [edit: filing now so we can discuss inclusion in 2.1.0]


Discussion:
I'm more in favour of 2 (and also 1) than 3, just because altering the user's certs without their knowledge gives me the heebie-jeebies, but I'm not 100% against it.
Yeah, that's very fair. Ok, maybe we can talk about it tomorrow and see what people think. The downside of 2 is that we'd need to settle on some translation strings pretty quickly.  
Per our discussion, [here's an implementation](https://gist.github.com/rocodes/b9d26b0405473090137b7249512d504d) of the third option. I've tested it on Tails 4.13 and 4.18. If this seems like roughly an approach we like, I'll submit a PR.    
A little more digging, a little more stress. 

My proposed method for validating the new cert was going to be to rely on the `DST_ROOT_CA_X3.pem` that [cross-signs the new cert](https://letsencrypt.org/certificates/) and is already installed on Tails workstations. However, the x3 cert is [expiring in 3 days](https://letsencrypt.org/docs/dst-root-ca-x3-expiration-september-2021/), so after the 30th, the verification will fail with `error 10 at 1 depth lookup: certificate has expired`.

The other way we could do any kind of verification on the cert is to also download it from the LetsEncrypt website, and only install it if the versions from the tails website and the LE website match.

Note: the Tails team does not include any kind of verification in their docs. 
 
Feedback welcome!
If we can't (or don't want to) ignore the expiry time with something like the `-no_check_time` flag, then the fallback option would just be to do the Tails version detection and throw up a dialog to users encouraging them to manually upgrade. Even just a system notification would be better than nothing IMO

Resolving patch:
diff --git a/install_files/ansible-base/roles/tails-config/files/securedrop_init.py b/install_files/ansible-base/roles/tails-config/files/securedrop_init.py
--- a/install_files/ansible-base/roles/tails-config/files/securedrop_init.py
+++ b/install_files/ansible-base/roles/tails-config/files/securedrop_init.py
@@ -7,7 +7,8 @@
 import sys
 import subprocess
 
-from shutil import copyfile
+import tempfile
+from shutil import copyfile, copyfileobj
 
 
 # check for root
@@ -148,3 +149,70 @@
 if b'Update needed' in output or os.path.exists(flag_location):
     # Start the SecureDrop updater GUI.
     subprocess.Popen(['python3', path_gui_updater], env=env)
+
+# Check for Tails < 4.19 and apply a fix to the auto-updater.
+# See https://tails.boum.org/news/version_4.18/
+# (Suggested removal: 2022/01)
+tails_4_min_version = 19
+needs_update = False
+tails_current_version = None
+
+with open('/etc/os-release') as file:
+    for line in file:
+        try:
+            k, v = line.strip().split("=")
+            if k == "TAILS_VERSION_ID":
+                tails_current_version = v.strip("\"").split(".")
+        except ValueError:
+            continue
+
+if tails_current_version:
+    try:
+        needs_update = (len(tails_current_version) >= 2 and
+                        int(tails_current_version[1]) < tails_4_min_version)
+
+    except (TypeError, ValueError):
+        sys.exit(0)  # Don't break tailsconfig trying to fix this
+
+    if needs_update:
+        cert_name = 'isrg-root-x1-cross-signed.pem'
+        pem_file = tempfile.NamedTemporaryFile(delete=True)
+
+        try:
+            subprocess.call(['torsocks', 'curl', '--silent',
+                             'https://tails.boum.org/' + cert_name],
+                            stdout=pem_file, env=env)
+
+            # Verify against /etc/ssl/certs/DST_Root_CA_X3.pem, which cross-signs
+            # the new LetsEncrypt cert but is expiring
+            verify_proc = subprocess.check_output(['openssl', 'verify',
+                                                   '-no_check_time', '-no-CApath',
+                                                   '-CAfile',
+                                                   '/etc/ssl/certs/DST_Root_CA_X3.pem',
+                                                   pem_file.name],
+                                                  universal_newlines=True, env=env)
+
+            if 'OK' in verify_proc:
+
+                # Updating the cert chain requires sudo privileges
+                os.setresgid(0, 0, -1)
+                os.setresuid(0, 0, -1)
+
+                with open('/usr/local/etc/ssl/certs/tails.boum.org-CA.pem', 'a') as chain:
+                    pem_file.seek(0)
+                    copyfileobj(pem_file, chain)
+
+                # As amnesia user, start updater GUI
+                os.setresgid(amnesia_gid, amnesia_gid, -1)
+                os.setresuid(amnesia_uid, amnesia_uid, -1)
+                restart_proc = subprocess.call(['systemctl', '--user', 'restart',
+                                                'tails-upgrade-frontend'], env=env)
+
+        except subprocess.CalledProcessError:
+            sys.exit(0)   # Don't break tailsconfig trying to fix this
+
+        except IOError:
+            sys.exit(0)
+
+        finally:
+            pem_file.close()


Test patch: