|
| 1 | +From b9eb9f73e0f77acdc89bd85773a13f95bcc2d81b Mon Sep 17 00:00:00 2001 |
| 2 | +From: Victor Stinner <vstinner@python.org> |
| 3 | +Date: Wed, 25 Mar 2026 07:44:47 +0100 |
| 4 | +Subject: [PATCH] gh-146207: Add support for OpenSSL 4.0.0 alpha1 (GH-146217) |
| 5 | +MIME-Version: 1.0 |
| 6 | +Content-Type: text/plain; charset=UTF-8 |
| 7 | +Content-Transfer-Encoding: 8bit |
| 8 | + |
| 9 | +OpenSSL 4.0.0 alpha1 removed these functions: |
| 10 | + |
| 11 | +* SSLv3_method() |
| 12 | +* TLSv1_method() |
| 13 | +* TLSv1_1_method() |
| 14 | +* TLSv1_2_method() |
| 15 | + |
| 16 | +Other changes: |
| 17 | + |
| 18 | +* Update test_openssl_version(). |
| 19 | +* Update multissltests.py for OpenSSL 4. |
| 20 | +* Add const qualifier to fix compiler warnings. |
| 21 | +(cherry picked from commit 3364e7e62fa24d0e19133fb0f90b1c24ef1110c5) |
| 22 | + |
| 23 | +Co-authored-by: Victor Stinner <vstinner@python.org> |
| 24 | +Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> |
| 25 | +--- |
| 26 | + Lib/test/test_ssl.py | 52 ++++++++++++++++++++------------------ |
| 27 | + Modules/_ssl.c | 27 ++++++++++++++++---- |
| 28 | + Modules/_ssl/cert.c | 3 ++- |
| 29 | + Tools/ssl/multissltests.py | 7 ++++- |
| 30 | + 4 files changed, 58 insertions(+), 31 deletions(-) |
| 31 | + |
| 32 | +diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py |
| 33 | +index 423d0292b4ebf8..0eced257a55333 100644 |
| 34 | +--- a/Lib/test/test_ssl.py |
| 35 | ++++ b/Lib/test/test_ssl.py |
| 36 | +@@ -380,7 +380,7 @@ def test_constants(self): |
| 37 | + ssl.OP_NO_COMPRESSION |
| 38 | + self.assertEqual(ssl.HAS_SNI, True) |
| 39 | + self.assertEqual(ssl.HAS_ECDH, True) |
| 40 | +- self.assertEqual(ssl.HAS_TLSv1_2, True) |
| 41 | ++ self.assertIsInstance(ssl.HAS_TLSv1_2, bool) |
| 42 | + self.assertEqual(ssl.HAS_TLSv1_3, True) |
| 43 | + ssl.OP_NO_SSLv2 |
| 44 | + ssl.OP_NO_SSLv3 |
| 45 | +@@ -571,11 +571,11 @@ def test_openssl_version(self): |
| 46 | + # Some sanity checks follow |
| 47 | + # >= 1.1.1 |
| 48 | + self.assertGreaterEqual(n, 0x10101000) |
| 49 | +- # < 4.0 |
| 50 | +- self.assertLess(n, 0x40000000) |
| 51 | ++ # < 5.0 |
| 52 | ++ self.assertLess(n, 0x50000000) |
| 53 | + major, minor, fix, patch, status = t |
| 54 | + self.assertGreaterEqual(major, 1) |
| 55 | +- self.assertLess(major, 4) |
| 56 | ++ self.assertLess(major, 5) |
| 57 | + self.assertGreaterEqual(minor, 0) |
| 58 | + self.assertLess(minor, 256) |
| 59 | + self.assertGreaterEqual(fix, 0) |
| 60 | +@@ -641,12 +641,14 @@ def test_openssl111_deprecations(self): |
| 61 | + ssl.OP_NO_TLSv1_2, |
| 62 | + ssl.OP_NO_TLSv1_3 |
| 63 | + ] |
| 64 | +- protocols = [ |
| 65 | +- ssl.PROTOCOL_TLSv1, |
| 66 | +- ssl.PROTOCOL_TLSv1_1, |
| 67 | +- ssl.PROTOCOL_TLSv1_2, |
| 68 | +- ssl.PROTOCOL_TLS |
| 69 | +- ] |
| 70 | ++ protocols = [] |
| 71 | ++ if hasattr(ssl, 'PROTOCOL_TLSv1'): |
| 72 | ++ protocols.append(ssl.PROTOCOL_TLSv1) |
| 73 | ++ if hasattr(ssl, 'PROTOCOL_TLSv1_1'): |
| 74 | ++ protocols.append(ssl.PROTOCOL_TLSv1_1) |
| 75 | ++ if hasattr(ssl, 'PROTOCOL_TLSv1_2'): |
| 76 | ++ protocols.append(ssl.PROTOCOL_TLSv1_2) |
| 77 | ++ protocols.append(ssl.PROTOCOL_TLS) |
| 78 | + versions = [ |
| 79 | + ssl.TLSVersion.SSLv3, |
| 80 | + ssl.TLSVersion.TLSv1, |
| 81 | +@@ -1140,6 +1142,7 @@ def test_min_max_version(self): |
| 82 | + ssl.TLSVersion.TLSv1, |
| 83 | + ssl.TLSVersion.TLSv1_1, |
| 84 | + ssl.TLSVersion.TLSv1_2, |
| 85 | ++ ssl.TLSVersion.TLSv1_3, |
| 86 | + ssl.TLSVersion.SSLv3, |
| 87 | + } |
| 88 | + ) |
| 89 | +@@ -1153,7 +1156,7 @@ def test_min_max_version(self): |
| 90 | + with self.assertRaises(ValueError): |
| 91 | + ctx.minimum_version = 42 |
| 92 | + |
| 93 | +- if has_tls_protocol(ssl.PROTOCOL_TLSv1_1): |
| 94 | ++ if has_tls_protocol('PROTOCOL_TLSv1_1'): |
| 95 | + ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1_1) |
| 96 | + |
| 97 | + self.assertIn( |
| 98 | +@@ -1605,23 +1608,24 @@ def test__create_stdlib_context(self): |
| 99 | + self.assertFalse(ctx.check_hostname) |
| 100 | + self._assert_context_options(ctx) |
| 101 | + |
| 102 | +- if has_tls_protocol(ssl.PROTOCOL_TLSv1): |
| 103 | ++ if has_tls_protocol('PROTOCOL_TLSv1'): |
| 104 | + with warnings_helper.check_warnings(): |
| 105 | + ctx = ssl._create_stdlib_context(ssl.PROTOCOL_TLSv1) |
| 106 | + self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLSv1) |
| 107 | + self.assertEqual(ctx.verify_mode, ssl.CERT_NONE) |
| 108 | + self._assert_context_options(ctx) |
| 109 | + |
| 110 | +- with warnings_helper.check_warnings(): |
| 111 | +- ctx = ssl._create_stdlib_context( |
| 112 | +- ssl.PROTOCOL_TLSv1_2, |
| 113 | +- cert_reqs=ssl.CERT_REQUIRED, |
| 114 | +- check_hostname=True |
| 115 | +- ) |
| 116 | +- self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLSv1_2) |
| 117 | +- self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED) |
| 118 | +- self.assertTrue(ctx.check_hostname) |
| 119 | +- self._assert_context_options(ctx) |
| 120 | ++ if has_tls_protocol('PROTOCOL_TLSv1_2'): |
| 121 | ++ with warnings_helper.check_warnings(): |
| 122 | ++ ctx = ssl._create_stdlib_context( |
| 123 | ++ ssl.PROTOCOL_TLSv1_2, |
| 124 | ++ cert_reqs=ssl.CERT_REQUIRED, |
| 125 | ++ check_hostname=True |
| 126 | ++ ) |
| 127 | ++ self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLSv1_2) |
| 128 | ++ self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED) |
| 129 | ++ self.assertTrue(ctx.check_hostname) |
| 130 | ++ self._assert_context_options(ctx) |
| 131 | + |
| 132 | + ctx = ssl._create_stdlib_context(purpose=ssl.Purpose.CLIENT_AUTH) |
| 133 | + self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLS_SERVER) |
| 134 | +@@ -3517,10 +3521,10 @@ def test_protocol_tlsv1_2(self): |
| 135 | + client_options=ssl.OP_NO_TLSv1_2) |
| 136 | + |
| 137 | + try_protocol_combo(ssl.PROTOCOL_TLS, ssl.PROTOCOL_TLSv1_2, 'TLSv1.2') |
| 138 | +- if has_tls_protocol(ssl.PROTOCOL_TLSv1): |
| 139 | ++ if has_tls_protocol('PROTOCOL_TLSv1'): |
| 140 | + try_protocol_combo(ssl.PROTOCOL_TLSv1_2, ssl.PROTOCOL_TLSv1, False) |
| 141 | + try_protocol_combo(ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_TLSv1_2, False) |
| 142 | +- if has_tls_protocol(ssl.PROTOCOL_TLSv1_1): |
| 143 | ++ if has_tls_protocol('PROTOCOL_TLSv1_1'): |
| 144 | + try_protocol_combo(ssl.PROTOCOL_TLSv1_2, ssl.PROTOCOL_TLSv1_1, False) |
| 145 | + try_protocol_combo(ssl.PROTOCOL_TLSv1_1, ssl.PROTOCOL_TLSv1_2, False) |
| 146 | + |
| 147 | +diff --git a/Modules/_ssl.c b/Modules/_ssl.c |
| 148 | +index a24b6b30b2f648..60a3d3f6a8fc90 100644 |
| 149 | +--- a/Modules/_ssl.c |
| 150 | ++++ b/Modules/_ssl.c |
| 151 | +@@ -134,6 +134,17 @@ static void _PySSLFixErrno(void) { |
| 152 | + #error Unsupported OpenSSL version |
| 153 | + #endif |
| 154 | + |
| 155 | ++#if (OPENSSL_VERSION_NUMBER >= 0x40000000L) |
| 156 | ++# define OPENSSL_NO_SSL3 |
| 157 | ++# define OPENSSL_NO_TLS1 |
| 158 | ++# define OPENSSL_NO_TLS1_1 |
| 159 | ++# define OPENSSL_NO_TLS1_2 |
| 160 | ++# define OPENSSL_NO_SSL3_METHOD |
| 161 | ++# define OPENSSL_NO_TLS1_METHOD |
| 162 | ++# define OPENSSL_NO_TLS1_1_METHOD |
| 163 | ++# define OPENSSL_NO_TLS1_2_METHOD |
| 164 | ++#endif |
| 165 | ++ |
| 166 | + /* OpenSSL API 1.1.0+ does not include version methods */ |
| 167 | + #ifndef OPENSSL_NO_SSL3_METHOD |
| 168 | + extern const SSL_METHOD *SSLv3_method(void); |
| 169 | +@@ -1133,7 +1144,7 @@ _asn1obj2py(_sslmodulestate *state, const ASN1_OBJECT *name, int no_name) |
| 170 | + |
| 171 | + static PyObject * |
| 172 | + _create_tuple_for_attribute(_sslmodulestate *state, |
| 173 | +- ASN1_OBJECT *name, ASN1_STRING *value) |
| 174 | ++ const ASN1_OBJECT *name, const ASN1_STRING *value) |
| 175 | + { |
| 176 | + Py_ssize_t buflen; |
| 177 | + PyObject *pyattr; |
| 178 | +@@ -1162,16 +1173,16 @@ _create_tuple_for_attribute(_sslmodulestate *state, |
| 179 | + } |
| 180 | + |
| 181 | + static PyObject * |
| 182 | +-_create_tuple_for_X509_NAME (_sslmodulestate *state, X509_NAME *xname) |
| 183 | ++_create_tuple_for_X509_NAME(_sslmodulestate *state, const X509_NAME *xname) |
| 184 | + { |
| 185 | + PyObject *dn = NULL; /* tuple which represents the "distinguished name" */ |
| 186 | + PyObject *rdn = NULL; /* tuple to hold a "relative distinguished name" */ |
| 187 | + PyObject *rdnt; |
| 188 | + PyObject *attr = NULL; /* tuple to hold an attribute */ |
| 189 | + int entry_count = X509_NAME_entry_count(xname); |
| 190 | +- X509_NAME_ENTRY *entry; |
| 191 | +- ASN1_OBJECT *name; |
| 192 | +- ASN1_STRING *value; |
| 193 | ++ const X509_NAME_ENTRY *entry; |
| 194 | ++ const ASN1_OBJECT *name; |
| 195 | ++ const ASN1_STRING *value; |
| 196 | + int index_counter; |
| 197 | + int rdn_level = -1; |
| 198 | + int retcode; |
| 199 | +@@ -6510,9 +6521,15 @@ sslmodule_init_constants(PyObject *m) |
| 200 | + ADD_INT_CONST("PROTOCOL_TLS", PY_SSL_VERSION_TLS); |
| 201 | + ADD_INT_CONST("PROTOCOL_TLS_CLIENT", PY_SSL_VERSION_TLS_CLIENT); |
| 202 | + ADD_INT_CONST("PROTOCOL_TLS_SERVER", PY_SSL_VERSION_TLS_SERVER); |
| 203 | ++#ifndef OPENSSL_NO_TLS1 |
| 204 | + ADD_INT_CONST("PROTOCOL_TLSv1", PY_SSL_VERSION_TLS1); |
| 205 | ++#endif |
| 206 | ++#ifndef OPENSSL_NO_TLS1_1 |
| 207 | + ADD_INT_CONST("PROTOCOL_TLSv1_1", PY_SSL_VERSION_TLS1_1); |
| 208 | ++#endif |
| 209 | ++#ifndef OPENSSL_NO_TLS1_2 |
| 210 | + ADD_INT_CONST("PROTOCOL_TLSv1_2", PY_SSL_VERSION_TLS1_2); |
| 211 | ++#endif |
| 212 | + |
| 213 | + #define ADD_OPTION(NAME, VALUE) if (sslmodule_add_option(m, NAME, (VALUE)) < 0) return -1 |
| 214 | + |
| 215 | +diff --git a/Modules/_ssl/cert.c b/Modules/_ssl/cert.c |
| 216 | +index f2e7be896687c8..061b0fb31716a4 100644 |
| 217 | +--- a/Modules/_ssl/cert.c |
| 218 | ++++ b/Modules/_ssl/cert.c |
| 219 | +@@ -128,7 +128,8 @@ _ssl_Certificate_get_info_impl(PySSLCertificate *self) |
| 220 | + } |
| 221 | + |
| 222 | + static PyObject* |
| 223 | +-_x509name_print(_sslmodulestate *state, X509_NAME *name, int indent, unsigned long flags) |
| 224 | ++_x509name_print(_sslmodulestate *state, const X509_NAME *name, |
| 225 | ++ int indent, unsigned long flags) |
| 226 | + { |
| 227 | + PyObject *res; |
| 228 | + BIO *biobuf; |
| 229 | +diff --git a/Tools/ssl/multissltests.py b/Tools/ssl/multissltests.py |
| 230 | +index 86baf8a3a74bd4..9019b2bcf38768 100755 |
| 231 | +--- a/Tools/ssl/multissltests.py |
| 232 | ++++ b/Tools/ssl/multissltests.py |
| 233 | +@@ -414,9 +414,11 @@ class BuildOpenSSL(AbstractBuilder): |
| 234 | + def _post_install(self): |
| 235 | + if self.version.startswith("3."): |
| 236 | + self._post_install_3xx() |
| 237 | ++ elif self.version.startswith("4."): |
| 238 | ++ self._post_install_4xx() |
| 239 | + |
| 240 | + def _build_src(self, config_args=()): |
| 241 | +- if self.version.startswith("3."): |
| 242 | ++ if self.version.startswith(("3.", "4.")): |
| 243 | + config_args += ("enable-fips",) |
| 244 | + super()._build_src(config_args) |
| 245 | + |
| 246 | +@@ -432,6 +434,9 @@ def _post_install_3xx(self): |
| 247 | + lib64 = self.lib_dir + "64" |
| 248 | + os.symlink(lib64, self.lib_dir) |
| 249 | + |
| 250 | ++ def _post_install_4xx(self): |
| 251 | ++ self._post_install_3xx() |
| 252 | ++ |
| 253 | + @property |
| 254 | + def short_version(self): |
| 255 | + """Short version for OpenSSL download URL""" |
0 commit comments