Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame^] | 1 | From 758e7463c104f71b810c8588166747eeab6148d7 Mon Sep 17 00:00:00 2001 |
| 2 | From: Christian Heimes <christian@python.org> |
| 3 | Date: Sat, 10 Sep 2016 22:43:48 +0200 |
| 4 | Subject: [PATCH 1/4] Issue 28043: SSLContext has improved default settings |
| 5 | |
| 6 | The options OP_NO_COMPRESSION, OP_CIPHER_SERVER_PREFERENCE, OP_SINGLE_DH_USE, OP_SINGLE_ECDH_USE, OP_NO_SSLv2 (except for PROTOCOL_SSLv2), and OP_NO_SSLv3 (except for PROTOCOL_SSLv3) are set by default. The initial cipher suite list contains only HIGH ciphers, no NULL ciphers and MD5 ciphers (except for PROTOCOL_SSLv2). |
| 7 | |
| 8 | Upstream-Status: Backport |
| 9 | [https://github.com/python/cpython/commit/358cfd426ccc0fcd6a7940d306602138e76420ae] |
| 10 | |
| 11 | Signed-off-by: Anuj Mittal <anuj.mittal@intel.com> |
| 12 | --- |
| 13 | Doc/library/ssl.rst | 9 ++++++- |
| 14 | Lib/ssl.py | 30 +++++---------------- |
| 15 | Lib/test/test_ssl.py | 62 +++++++++++++++++++++++--------------------- |
| 16 | Modules/_ssl.c | 31 ++++++++++++++++++++++ |
| 17 | 4 files changed, 78 insertions(+), 54 deletions(-) |
| 18 | |
| 19 | diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst |
| 20 | index a2f008346b..14f2d68217 100644 |
| 21 | --- a/Doc/library/ssl.rst |
| 22 | +++ b/Doc/library/ssl.rst |
| 23 | @@ -1151,7 +1151,14 @@ to speed up repeated connections from the same clients. |
| 24 | |
| 25 | .. versionchanged:: 3.5.3 |
| 26 | |
| 27 | - :data:`PROTOCOL_TLS` is the default value. |
| 28 | + The context is created with secure default values. The options |
| 29 | + :data:`OP_NO_COMPRESSION`, :data:`OP_CIPHER_SERVER_PREFERENCE`, |
| 30 | + :data:`OP_SINGLE_DH_USE`, :data:`OP_SINGLE_ECDH_USE`, |
| 31 | + :data:`OP_NO_SSLv2` (except for :data:`PROTOCOL_SSLv2`), |
| 32 | + and :data:`OP_NO_SSLv3` (except for :data:`PROTOCOL_SSLv3`) are |
| 33 | + set by default. The initial cipher suite list contains only ``HIGH`` |
| 34 | + ciphers, no ``NULL`` ciphers and no ``MD5`` ciphers (except for |
| 35 | + :data:`PROTOCOL_SSLv2`). |
| 36 | |
| 37 | |
| 38 | :class:`SSLContext` objects have the following methods and attributes: |
| 39 | diff --git a/Lib/ssl.py b/Lib/ssl.py |
| 40 | index e1913904f3..4d302a78fa 100644 |
| 41 | --- a/Lib/ssl.py |
| 42 | +++ b/Lib/ssl.py |
| 43 | @@ -446,32 +446,16 @@ def create_default_context(purpose=Purpose.SERVER_AUTH, *, cafile=None, |
| 44 | if not isinstance(purpose, _ASN1Object): |
| 45 | raise TypeError(purpose) |
| 46 | |
| 47 | + # SSLContext sets OP_NO_SSLv2, OP_NO_SSLv3, OP_NO_COMPRESSION, |
| 48 | + # OP_CIPHER_SERVER_PREFERENCE, OP_SINGLE_DH_USE and OP_SINGLE_ECDH_USE |
| 49 | + # by default. |
| 50 | context = SSLContext(PROTOCOL_TLS) |
| 51 | |
| 52 | - # SSLv2 considered harmful. |
| 53 | - context.options |= OP_NO_SSLv2 |
| 54 | - |
| 55 | - # SSLv3 has problematic security and is only required for really old |
| 56 | - # clients such as IE6 on Windows XP |
| 57 | - context.options |= OP_NO_SSLv3 |
| 58 | - |
| 59 | - # disable compression to prevent CRIME attacks (OpenSSL 1.0+) |
| 60 | - context.options |= getattr(_ssl, "OP_NO_COMPRESSION", 0) |
| 61 | - |
| 62 | if purpose == Purpose.SERVER_AUTH: |
| 63 | # verify certs and host name in client mode |
| 64 | context.verify_mode = CERT_REQUIRED |
| 65 | context.check_hostname = True |
| 66 | elif purpose == Purpose.CLIENT_AUTH: |
| 67 | - # Prefer the server's ciphers by default so that we get stronger |
| 68 | - # encryption |
| 69 | - context.options |= getattr(_ssl, "OP_CIPHER_SERVER_PREFERENCE", 0) |
| 70 | - |
| 71 | - # Use single use keys in order to improve forward secrecy |
| 72 | - context.options |= getattr(_ssl, "OP_SINGLE_DH_USE", 0) |
| 73 | - context.options |= getattr(_ssl, "OP_SINGLE_ECDH_USE", 0) |
| 74 | - |
| 75 | - # disallow ciphers with known vulnerabilities |
| 76 | context.set_ciphers(_RESTRICTED_SERVER_CIPHERS) |
| 77 | |
| 78 | if cafile or capath or cadata: |
| 79 | @@ -497,12 +481,10 @@ def _create_unverified_context(protocol=PROTOCOL_TLS, *, cert_reqs=None, |
| 80 | if not isinstance(purpose, _ASN1Object): |
| 81 | raise TypeError(purpose) |
| 82 | |
| 83 | + # SSLContext sets OP_NO_SSLv2, OP_NO_SSLv3, OP_NO_COMPRESSION, |
| 84 | + # OP_CIPHER_SERVER_PREFERENCE, OP_SINGLE_DH_USE and OP_SINGLE_ECDH_USE |
| 85 | + # by default. |
| 86 | context = SSLContext(protocol) |
| 87 | - # SSLv2 considered harmful. |
| 88 | - context.options |= OP_NO_SSLv2 |
| 89 | - # SSLv3 has problematic security and is only required for really old |
| 90 | - # clients such as IE6 on Windows XP |
| 91 | - context.options |= OP_NO_SSLv3 |
| 92 | |
| 93 | if cert_reqs is not None: |
| 94 | context.verify_mode = cert_reqs |
| 95 | diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py |
| 96 | index ffb7314f57..f91af7bd05 100644 |
| 97 | --- a/Lib/test/test_ssl.py |
| 98 | +++ b/Lib/test/test_ssl.py |
| 99 | @@ -73,6 +73,12 @@ NULLBYTECERT = data_file("nullbytecert.pem") |
| 100 | DHFILE = data_file("dh1024.pem") |
| 101 | BYTES_DHFILE = os.fsencode(DHFILE) |
| 102 | |
| 103 | +# Not defined in all versions of OpenSSL |
| 104 | +OP_NO_COMPRESSION = getattr(ssl, "OP_NO_COMPRESSION", 0) |
| 105 | +OP_SINGLE_DH_USE = getattr(ssl, "OP_SINGLE_DH_USE", 0) |
| 106 | +OP_SINGLE_ECDH_USE = getattr(ssl, "OP_SINGLE_ECDH_USE", 0) |
| 107 | +OP_CIPHER_SERVER_PREFERENCE = getattr(ssl, "OP_CIPHER_SERVER_PREFERENCE", 0) |
| 108 | + |
| 109 | |
| 110 | def handle_error(prefix): |
| 111 | exc_format = ' '.join(traceback.format_exception(*sys.exc_info())) |
| 112 | @@ -839,8 +845,9 @@ class ContextTests(unittest.TestCase): |
| 113 | ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1) |
| 114 | # OP_ALL | OP_NO_SSLv2 | OP_NO_SSLv3 is the default value |
| 115 | default = (ssl.OP_ALL | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3) |
| 116 | - if not IS_LIBRESSL and ssl.OPENSSL_VERSION_INFO >= (1, 1, 0): |
| 117 | - default |= ssl.OP_NO_COMPRESSION |
| 118 | + # SSLContext also enables these by default |
| 119 | + default |= (OP_NO_COMPRESSION | OP_CIPHER_SERVER_PREFERENCE | |
| 120 | + OP_SINGLE_DH_USE | OP_SINGLE_ECDH_USE) |
| 121 | self.assertEqual(default, ctx.options) |
| 122 | ctx.options |= ssl.OP_NO_TLSv1 |
| 123 | self.assertEqual(default | ssl.OP_NO_TLSv1, ctx.options) |
| 124 | @@ -1205,16 +1212,29 @@ class ContextTests(unittest.TestCase): |
| 125 | stats["x509"] += 1 |
| 126 | self.assertEqual(ctx.cert_store_stats(), stats) |
| 127 | |
| 128 | + def _assert_context_options(self, ctx): |
| 129 | + self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2) |
| 130 | + if OP_NO_COMPRESSION != 0: |
| 131 | + self.assertEqual(ctx.options & OP_NO_COMPRESSION, |
| 132 | + OP_NO_COMPRESSION) |
| 133 | + if OP_SINGLE_DH_USE != 0: |
| 134 | + self.assertEqual(ctx.options & OP_SINGLE_DH_USE, |
| 135 | + OP_SINGLE_DH_USE) |
| 136 | + if OP_SINGLE_ECDH_USE != 0: |
| 137 | + self.assertEqual(ctx.options & OP_SINGLE_ECDH_USE, |
| 138 | + OP_SINGLE_ECDH_USE) |
| 139 | + if OP_CIPHER_SERVER_PREFERENCE != 0: |
| 140 | + self.assertEqual(ctx.options & OP_CIPHER_SERVER_PREFERENCE, |
| 141 | + OP_CIPHER_SERVER_PREFERENCE) |
| 142 | + |
| 143 | def test_create_default_context(self): |
| 144 | ctx = ssl.create_default_context() |
| 145 | + |
| 146 | self.assertEqual(ctx.protocol, ssl.PROTOCOL_SSLv23) |
| 147 | self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED) |
| 148 | self.assertTrue(ctx.check_hostname) |
| 149 | - self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2) |
| 150 | - self.assertEqual( |
| 151 | - ctx.options & getattr(ssl, "OP_NO_COMPRESSION", 0), |
| 152 | - getattr(ssl, "OP_NO_COMPRESSION", 0), |
| 153 | - ) |
| 154 | + self._assert_context_options(ctx) |
| 155 | + |
| 156 | |
| 157 | with open(SIGNING_CA) as f: |
| 158 | cadata = f.read() |
| 159 | @@ -1222,40 +1242,24 @@ class ContextTests(unittest.TestCase): |
| 160 | cadata=cadata) |
| 161 | self.assertEqual(ctx.protocol, ssl.PROTOCOL_SSLv23) |
| 162 | self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED) |
| 163 | - self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2) |
| 164 | - self.assertEqual( |
| 165 | - ctx.options & getattr(ssl, "OP_NO_COMPRESSION", 0), |
| 166 | - getattr(ssl, "OP_NO_COMPRESSION", 0), |
| 167 | - ) |
| 168 | + self._assert_context_options(ctx) |
| 169 | |
| 170 | ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) |
| 171 | self.assertEqual(ctx.protocol, ssl.PROTOCOL_SSLv23) |
| 172 | self.assertEqual(ctx.verify_mode, ssl.CERT_NONE) |
| 173 | - self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2) |
| 174 | - self.assertEqual( |
| 175 | - ctx.options & getattr(ssl, "OP_NO_COMPRESSION", 0), |
| 176 | - getattr(ssl, "OP_NO_COMPRESSION", 0), |
| 177 | - ) |
| 178 | - self.assertEqual( |
| 179 | - ctx.options & getattr(ssl, "OP_SINGLE_DH_USE", 0), |
| 180 | - getattr(ssl, "OP_SINGLE_DH_USE", 0), |
| 181 | - ) |
| 182 | - self.assertEqual( |
| 183 | - ctx.options & getattr(ssl, "OP_SINGLE_ECDH_USE", 0), |
| 184 | - getattr(ssl, "OP_SINGLE_ECDH_USE", 0), |
| 185 | - ) |
| 186 | + self._assert_context_options(ctx) |
| 187 | |
| 188 | def test__create_stdlib_context(self): |
| 189 | ctx = ssl._create_stdlib_context() |
| 190 | self.assertEqual(ctx.protocol, ssl.PROTOCOL_SSLv23) |
| 191 | self.assertEqual(ctx.verify_mode, ssl.CERT_NONE) |
| 192 | self.assertFalse(ctx.check_hostname) |
| 193 | - self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2) |
| 194 | + self._assert_context_options(ctx) |
| 195 | |
| 196 | ctx = ssl._create_stdlib_context(ssl.PROTOCOL_TLSv1) |
| 197 | self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLSv1) |
| 198 | self.assertEqual(ctx.verify_mode, ssl.CERT_NONE) |
| 199 | - self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2) |
| 200 | + self._assert_context_options(ctx) |
| 201 | |
| 202 | ctx = ssl._create_stdlib_context(ssl.PROTOCOL_TLSv1, |
| 203 | cert_reqs=ssl.CERT_REQUIRED, |
| 204 | @@ -1263,12 +1267,12 @@ class ContextTests(unittest.TestCase): |
| 205 | self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLSv1) |
| 206 | self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED) |
| 207 | self.assertTrue(ctx.check_hostname) |
| 208 | - self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2) |
| 209 | + self._assert_context_options(ctx) |
| 210 | |
| 211 | ctx = ssl._create_stdlib_context(purpose=ssl.Purpose.CLIENT_AUTH) |
| 212 | self.assertEqual(ctx.protocol, ssl.PROTOCOL_SSLv23) |
| 213 | self.assertEqual(ctx.verify_mode, ssl.CERT_NONE) |
| 214 | - self.assertEqual(ctx.options & ssl.OP_NO_SSLv2, ssl.OP_NO_SSLv2) |
| 215 | + self._assert_context_options(ctx) |
| 216 | |
| 217 | def test_check_hostname(self): |
| 218 | ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1) |
| 219 | diff --git a/Modules/_ssl.c b/Modules/_ssl.c |
| 220 | index 86482677ae..0d5c121d2c 100644 |
| 221 | --- a/Modules/_ssl.c |
| 222 | +++ b/Modules/_ssl.c |
| 223 | @@ -2330,6 +2330,7 @@ _ssl__SSLContext_impl(PyTypeObject *type, int proto_version) |
| 224 | PySSLContext *self; |
| 225 | long options; |
| 226 | SSL_CTX *ctx = NULL; |
| 227 | + int result; |
| 228 | #if defined(SSL_MODE_RELEASE_BUFFERS) |
| 229 | unsigned long libver; |
| 230 | #endif |
| 231 | @@ -2393,8 +2394,38 @@ _ssl__SSLContext_impl(PyTypeObject *type, int proto_version) |
| 232 | options |= SSL_OP_NO_SSLv2; |
| 233 | if (proto_version != PY_SSL_VERSION_SSL3) |
| 234 | options |= SSL_OP_NO_SSLv3; |
| 235 | + /* Minimal security flags for server and client side context. |
| 236 | + * Client sockets ignore server-side parameters. */ |
| 237 | +#ifdef SSL_OP_NO_COMPRESSION |
| 238 | + options |= SSL_OP_NO_COMPRESSION; |
| 239 | +#endif |
| 240 | +#ifdef SSL_OP_CIPHER_SERVER_PREFERENCE |
| 241 | + options |= SSL_OP_CIPHER_SERVER_PREFERENCE; |
| 242 | +#endif |
| 243 | +#ifdef SSL_OP_SINGLE_DH_USE |
| 244 | + options |= SSL_OP_SINGLE_DH_USE; |
| 245 | +#endif |
| 246 | +#ifdef SSL_OP_SINGLE_ECDH_USE |
| 247 | + options |= SSL_OP_SINGLE_ECDH_USE; |
| 248 | +#endif |
| 249 | SSL_CTX_set_options(self->ctx, options); |
| 250 | |
| 251 | + /* A bare minimum cipher list without completly broken cipher suites. |
| 252 | + * It's far from perfect but gives users a better head start. */ |
| 253 | + if (proto_version != PY_SSL_VERSION_SSL2) { |
| 254 | + result = SSL_CTX_set_cipher_list(ctx, "HIGH:!aNULL:!eNULL:!MD5"); |
| 255 | + } else { |
| 256 | + /* SSLv2 needs MD5 */ |
| 257 | + result = SSL_CTX_set_cipher_list(ctx, "HIGH:!aNULL:!eNULL"); |
| 258 | + } |
| 259 | + if (result == 0) { |
| 260 | + Py_DECREF(self); |
| 261 | + ERR_clear_error(); |
| 262 | + PyErr_SetString(PySSLErrorObject, |
| 263 | + "No cipher can be selected."); |
| 264 | + return NULL; |
| 265 | + } |
| 266 | + |
| 267 | #if defined(SSL_MODE_RELEASE_BUFFERS) |
| 268 | /* Set SSL_MODE_RELEASE_BUFFERS. This potentially greatly reduces memory |
| 269 | usage for no cost at all. However, don't do this for OpenSSL versions |
| 270 | -- |
| 271 | 2.17.1 |
| 272 | |