Repository: certbot/certbot

Issue:
Update the nginx plugin's TLS config to follow Mozilla's new recommendations
This can be found at https://ssl-config.mozilla.org. In general, doing this is a little tricky because we don't want to break people with old versions of nginx/OpenSSL. Looking at the code that is used to generate these config snippets may be useful for this which is found at https://github.com/mozilla/ssl-config-generator.

For now, I don't think we should be enabling HTTP2, HSTS, or OCSP stapling as part of this. We may want to reconsider doing this in the future, especially for HTTP2, but so far we've thought about these things as an optional security enhancement for Certbot and I think deciding to enable these features on all configs should be a separate issue.


Discussion:
>  In general, doing this is a little tricky because we don't want to break people with old versions of nginx/OpenSSL.

Could you say more about this? Which part of this might break these configurations, and are we choosing to support something that Mozilla doesn't think still needs to be supported?
When you go to https://ssl-config.mozilla.org/ the default nginx config it generates as of writing this is for nginx 1.17.0 and openssl 1.1.1c which were both released in May. When you have recent versions of both, you are recommended to enable the latest and greatest security features such as TLS 1.3 and disable problematic features like TLS session tickets.

Unfortunately, most people's versions of nginx and openssl are older than this though and not all of them support these features. You can specify an older version of nginx/openssl into Mozilla's config generator and it will leave these settings out of the generated config.

While we could just use the settings for the oldest possible configuration we're still supporting, I personally think we should dynamically use newer configuration options as they become available. Not doing this is what caused us to not disable TLS session tickets until recently and I think there's value in us actually trying to follow Mozilla's recommendations as close as we can.

Luckily, I suspect this will be pretty easy. I think whoever works on this issue should verify this, but I think the only things we have to worry about handling dynamically are TLS session tickets (which we already handle) and TLS 1.3.
Ah, gotcha. Yeah, looks like barring the things you mentioned in the original post, the only other changes are in this section:

```
# intermediate configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
```

The first two are explained [on the site](https://wiki.mozilla.org/Security/Server_Side_TLS). But as for `ssl_prefer_server_ciphers`, as far as I can tell that's `off` for all of their recommendations, even though we have it as `on`. Do you remember why we do that?
It's what they recommended in the [old version of the config](https://web.archive.org/web/20190425121155/https://mozilla.github.io/server-side-tls/ssl-config-generator/) before their recent update.

A description of why they made this change is at https://github.com/mozilla/server-side-tls/issues/260.
Ah, cool, so gotta be sure to keep that option coupled with offering a narrower range of secure-only ciphers.
Keeping in mind that you should probably double check everything that I'm saying to make sure I'm not misreading things, but I believe their current recommendations for their intermediate config always results in the same values for `ssl_ciphers` and `ssl_prefer_server_ciphers` for all versions of nginx and openssl.
You're right, I was confusing `ssl_protocols` and `ssl_ciphers`; it's just that older versions of Nginx won't have newer `ssl_protocols` available.

I am surprised to see that no matter how old you put either Nginx or OpenSSL, all the ciphers are always available. Must be that it's letting OpenSSL figure out dynamically if it has that cipher available.
It's also possible that their config generator is buggy here. I'm not aware of any current issues, but from watching their repos I've seen at least a couple bugs that have since been fixed caused by them not properly filtering values for older versions of different software.

I think it's probably worth us spinning up an ancient version of Debian/CentOS/Ubuntu and verifying that the recommended values work.
tested on centos6 with nginx 1.10.3, intermediate config, OpenSSL 1.0.1e-fips: works fine

how ancient do you think we can reasonably expect to get with distros we still support?
tested on debian jessie with nginx 1.6.2, intermediate config, OpenSSL 1.0.1t: works fine
for tls1.3, we need:
- nginx >= 1.13.0
- openssl >= 1.1.1 (being careful about them adding letters to their versions)

Resolving patch:
diff --git a/certbot-nginx/certbot_nginx/configurator.py b/certbot-nginx/certbot_nginx/configurator.py
--- a/certbot-nginx/certbot_nginx/configurator.py
+++ b/certbot-nginx/certbot_nginx/configurator.py
@@ -127,6 +127,8 @@ def mod_ssl_conf_src(self):
         config_filename = "options-ssl-nginx.conf"
         if self.version < (1, 5, 9):
             config_filename = "options-ssl-nginx-old.conf"
+        elif self.version < (1, 13, 0):
+            config_filename = "options-ssl-nginx-tls12-only.conf"
         return pkg_resources.resource_filename("certbot_nginx", config_filename)
 
     @property
diff --git a/certbot-nginx/certbot_nginx/constants.py b/certbot-nginx/certbot_nginx/constants.py
--- a/certbot-nginx/certbot_nginx/constants.py
+++ b/certbot-nginx/certbot_nginx/constants.py
@@ -23,10 +23,17 @@
 """Name of the hash of the updated or informed mod_ssl_conf as saved in `IConfig.config_dir`."""
 
 SSL_OPTIONS_HASHES_NEW = [
+    '108c4555058a087496a3893aea5d9e1cee0f20a3085d44a52dc1a66522299ac3',
+]
+"""SHA256 hashes of the contents of versions of MOD_SSL_CONF_SRC for nginx >= 1.13.0"""
+
+SSL_OPTIONS_HASHES_MEDIUM = [
     '63e2bddebb174a05c9d8a7cf2adf72f7af04349ba59a1a925fe447f73b2f1abf',
     '2901debc7ecbc10917edd9084c05464c9c5930b463677571eaf8c94bffd11ae2',
+    '30baca73ed9a5b0e9a69ea40e30482241d8b1a7343aa79b49dc5d7db0bf53b6c',
 ]
-"""SHA256 hashes of the contents of versions of MOD_SSL_CONF_SRC for nginx >= 1.5.9"""
+"""SHA256 hashes of the contents of versions of MOD_SSL_CONF_SRC for nginx >= 1.5.9
+   and nginx < 1.13.0"""
 
 ALL_SSL_OPTIONS_HASHES = [
     '0f81093a1465e3d4eaa8b0c14e77b2a2e93568b0fc1351c2b87893a95f0de87c',
@@ -36,7 +43,8 @@
     '394732f2bbe3e5e637c3fb5c6e980a1f1b90b01e2e8d6b7cff41dde16e2a756d',
     '4b16fec2bcbcd8a2f3296d886f17f9953ffdcc0af54582452ca1e52f5f776f16',
     'c052ffff0ad683f43bffe105f7c606b339536163490930e2632a335c8d191cc4',
-] + SSL_OPTIONS_HASHES_NEW
+    '02329eb19930af73c54b3632b3165d84571383b8c8c73361df940cb3894dd426',
+] + SSL_OPTIONS_HASHES_MEDIUM + SSL_OPTIONS_HASHES_NEW
 """SHA256 hashes of the contents of all versions of MOD_SSL_CONF_SRC"""
 
 def os_constant(key):


Test patch:
diff --git a/certbot-nginx/certbot_nginx/tests/configurator_test.py b/certbot-nginx/certbot_nginx/tests/configurator_test.py
--- a/certbot-nginx/certbot_nginx/tests/configurator_test.py
+++ b/certbot-nginx/certbot_nginx/tests/configurator_test.py
@@ -963,13 +963,39 @@ def test_current_file_hash_in_all_hashes(self):
             "Constants.ALL_SSL_OPTIONS_HASHES must be appended"
             " with the sha256 hash of self.config.mod_ssl_conf when it is updated.")
 
-    def test_old_nginx_version_uses_old_config(self):
+    def test_ssl_config_files_hash_in_all_hashes(self):
+        """
+        It is really critical that all TLS Nginx config files have their SHA256 hash registered in
+        constants.ALL_SSL_OPTIONS_HASHES. Otherwise Certbot will mistakenly assume that the config
+        file has been manually edited by the user, and will refuse to update it.
+        This test ensures that all necessary hashes are present.
+        """
+        from certbot_nginx.constants import ALL_SSL_OPTIONS_HASHES
+        import pkg_resources
+        all_files = [pkg_resources.resource_filename("certbot_nginx", x) for x in (
+          "options-ssl-nginx.conf",
+          "options-ssl-nginx-old.conf",
+          "options-ssl-nginx-tls12-only.conf"
+          )]
+        self.assertTrue(all_files)
+        for one_file in all_files:
+            file_hash = crypto_util.sha256sum(one_file)
+            self.assertTrue(file_hash in ALL_SSL_OPTIONS_HASHES,
+                            "Constants.ALL_SSL_OPTIONS_HASHES must be appended with the sha256 "
+                            "hash of {0} when it is updated.".format(one_file))
+
+    def test_nginx_version_uses_correct_config(self):
         self.config.version = (1, 5, 8)
         self.assertEqual(os.path.basename(self.config.mod_ssl_conf_src),
                          "options-ssl-nginx-old.conf")
         self._call()
         self._assert_current_file()
         self.config.version = (1, 5, 9)
+        self.assertEqual(os.path.basename(self.config.mod_ssl_conf_src),
+                         "options-ssl-nginx-tls12-only.conf")
+        self._call()
+        self._assert_current_file()
+        self.config.version = (1, 13, 0)
         self.assertEqual(os.path.basename(self.config.mod_ssl_conf_src),
                          "options-ssl-nginx.conf")