Repository: freedomofpress/securedrop

Issue:
[v3 onion migration] warn user if they enable v3 and have https enabled
In `securedrop-admin sdconfig` when a user runs with `v3_onion_services=True`, we should:

1. (minimum) If HTTPS is also enabled, it should print a warning, but not prevent the writing of the config. They should be warned that this change will apply their configured cert to both their v2 and v3 interfaces and until they have a v3 cert, they should not share or advertise their v3 source URL with sources.
2. (better) Inspect the names in the configured cert and warn the user only: when the cert hasn't been updated to include their v3 URLs (predicated on the user having enabled v3 once already) OR the v3 URLs don't exist locally (since this means they are enabling v3 onion services for the first time, they have to run securedrop-admin install at least once in order to get the new URLs).


Discussion:
Good news, I have found a cert issued by Digicert with a v3 onion URL (and a v2 onion URL) included in the subject alternative name field: https://crt.sh/?id=351449246. When using the v3 cert one sees in Tor Browser a little onion and padlock icon:

![Screen Shot 2019-08-22 at 12 48 33 PM](https://user-images.githubusercontent.com/7832803/63533723-600f2d00-c4db-11e9-9840-42cd84c9d899.png)

so for this "better" implementation for this ticket we can: 1. parse cert, 2. look in common name, subject alternative name fields, 3. alert the user if either the v2 OR v3 URL is missing
oh yeah and there's also Cloudflare's  https://dns4torpnlfs2ifuz2s2yf3fc7rdmsbhm6rw75euj35pac6ap25zgqad.onion/ (Digicert)

Resolving patch:
diff --git a/admin/securedrop_admin/__init__.py b/admin/securedrop_admin/__init__.py
--- a/admin/securedrop_admin/__init__.py
+++ b/admin/securedrop_admin/__init__.py
@@ -446,6 +446,25 @@ def update_config(self):
         self.save()
         self.validate_gpg_keys()
         self.validate_journalist_alert_email()
+        self.validate_https_and_v3()
+        return True
+
+    def validate_https_and_v3(self):
+        """
+        Checks if https is enabled with v3 onion service.
+
+        :returns: False if both v3 and https enabled, True otherwise.
+        """
+        warning_msg = ("You have configured HTTPS on your source interface "
+                       "and v3 onion services. "
+                       "IMPORTANT: Ensure that you update your certificate "
+                       "to include your v3 source URL before advertising "
+                       "it to sources! ")
+
+        if self.config.get("v3_onion_services", False) and \
+                self.config.get("securedrop_app_https_certificate_cert_src"):
+            print(warning_msg)
+            return False
         return True
 
     def check_for_v2_onion(self):


Test patch:
diff --git a/admin/tests/test_securedrop-admin.py b/admin/tests/test_securedrop-admin.py
--- a/admin/tests/test_securedrop-admin.py
+++ b/admin/tests/test_securedrop-admin.py
@@ -1042,3 +1042,22 @@ def test_find_or_generate_new_torv3_keys_subsequent_run(tmpdir, capsys):
         v3_onion_service_keys = json.load(f)
 
     assert v3_onion_service_keys == old_keys
+
+
+def test_v3_and_https_cert_message(tmpdir, capsys):
+    args = argparse.Namespace(site_config='UNKNOWN',
+                              ansible_path='tests/files',
+                              app_path=dirname(__file__))
+    site_config = securedrop_admin.SiteConfig(args)
+    site_config.config = {"v3_onion_services": False,
+                          "securedrop_app_https_certificate_cert_src": "ab.crt"}  # noqa: E501
+    # This should return True as v3 is not setup
+    assert site_config.validate_https_and_v3()
+
+    # This should return False as v3 and https are both setup
+    site_config.config.update({"v3_onion_services": True})
+    assert not site_config.validate_https_and_v3()
+
+    # This should return True as https is not setup
+    site_config.config.update({"securedrop_app_https_certificate_cert_src": ""})  # noqa: E501
+    assert site_config.validate_https_and_v3()