diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index 42fcd38cc7d3df..3ed1c515d86d85 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -125,7 +125,8 @@ Context creation A convenience function helps create :class:`SSLContext` objects for common purposes. -.. function:: create_default_context(purpose=Purpose.SERVER_AUTH, cafile=None, capath=None, cadata=None) +.. function:: create_default_context(purpose=Purpose.SERVER_AUTH, *,\ + cafile=None, capath=None, cadata=None) Return a new :class:`SSLContext` object with default settings for the given *purpose*. The settings are chosen by the :mod:`ssl` module, @@ -215,6 +216,25 @@ purposes. :data:`VERIFY_X509_STRICT` in its default verify flags. +Signature algorithms +^^^^^^^^^^^^^^^^^^^^ + +.. function:: get_sigalgs() + + Return a list of available TLS signature algorithm names used + by servers to complete the TLS handshake or clients requesting + certificate-based authentication. For example:: + + >>> ssl.get_sigalgs() # doctest: +SKIP + ['ecdsa_secp256r1_sha256', 'ecdsa_secp384r1_sha384', ...] + + These names can be used when building string values to pass to the + :meth:`SSLContext.set_client_sigalgs` and + :meth:`SSLContext.set_server_sigalgs` methods. + + .. versionadded:: next + + Exceptions ^^^^^^^^^^ @@ -314,7 +334,7 @@ Exceptions Random generation ^^^^^^^^^^^^^^^^^ -.. function:: RAND_bytes(num) +.. function:: RAND_bytes(num, /) Return *num* cryptographically strong pseudo-random bytes. Raises an :class:`SSLError` if the PRNG has not been seeded with enough data or if the @@ -338,7 +358,7 @@ Random generation :func:`ssl.RAND_egd` and :func:`ssl.RAND_add` to increase the randomness of the pseudo-random number generator. -.. function:: RAND_add(bytes, entropy) +.. function:: RAND_add(bytes, entropy, /) Mix the given *bytes* into the SSL pseudo-random number generator. The parameter *entropy* (a float) is a lower bound on the entropy contained in @@ -406,12 +426,12 @@ Certificate handling .. versionchanged:: 3.10 The *timeout* parameter was added. -.. function:: DER_cert_to_PEM_cert(DER_cert_bytes) +.. function:: DER_cert_to_PEM_cert(der_cert_bytes) Given a certificate as a DER-encoded blob of bytes, returns a PEM-encoded string version of the same certificate. -.. function:: PEM_cert_to_DER_cert(PEM_cert_string) +.. function:: PEM_cert_to_DER_cert(pem_cert_string) Given a certificate as an ASCII PEM string, returns a DER-encoded sequence of bytes for that same certificate. @@ -1141,10 +1161,10 @@ SSL sockets also have the following additional methods and attributes: .. deprecated:: 3.6 Use :meth:`~SSLSocket.recv` instead of :meth:`~SSLSocket.read`. -.. method:: SSLSocket.write(buf) +.. method:: SSLSocket.write(data) - Write *buf* to the SSL socket and return the number of bytes written. The - *buf* argument must be an object supporting the buffer interface. + Write *data* to the SSL socket and return the number of bytes written. The + *data* argument must be an object supporting the buffer interface. Raise :exc:`SSLWantReadError` or :exc:`SSLWantWriteError` if the socket is :ref:`non-blocking ` and the write would block. @@ -1154,7 +1174,7 @@ SSL sockets also have the following additional methods and attributes: .. versionchanged:: 3.5 The socket timeout is no longer reset each time bytes are received or sent. - The socket timeout is now the maximum total duration to write *buf*. + The socket timeout is now the maximum total duration to write *data*. .. deprecated:: 3.6 Use :meth:`~SSLSocket.send` instead of :meth:`~SSLSocket.write`. @@ -1171,10 +1191,13 @@ SSL sockets also have the following additional methods and attributes: :meth:`~socket.socket.recv` and :meth:`~socket.socket.send` instead of these methods. -.. method:: SSLSocket.do_handshake() +.. method:: SSLSocket.do_handshake(block=False) Perform the SSL setup handshake. + If *block* is true and the timeout obtained by :meth:`~socket.gettimeout` + is zero, the socket is set in blocking mode until the handshake is performed. + .. versionchanged:: 3.4 The handshake method also performs :func:`match_hostname` when the :attr:`~SSLContext.check_hostname` attribute of the socket's @@ -1297,6 +1320,22 @@ SSL sockets also have the following additional methods and attributes: .. versionadded:: next +.. method:: SSLSocket.client_sigalg() + + Return the signature algorithm used for performing certificate-based client + authentication on this connection, or ``None`` if no connection has been + established or client authentication didn't occur. + + .. versionadded:: next + +.. method:: SSLSocket.server_sigalg() + + Return the signature algorithm used by the server to complete the TLS + handshake on this connection, or ``None`` if no connection has been + established or the cipher suite has no signature. + + .. versionadded:: next + .. method:: SSLSocket.compression() Return the compression algorithm being used as a string, or ``None`` @@ -1682,7 +1721,7 @@ to speed up repeated connections from the same clients. provided as part of the operating system, though, it is likely to be configured properly. -.. method:: SSLContext.set_ciphers(ciphers) +.. method:: SSLContext.set_ciphers(ciphers, /) Set the allowed ciphers for sockets created with this context when connecting using TLS 1.2 and earlier. The *ciphers* argument should @@ -1698,7 +1737,7 @@ to speed up repeated connections from the same clients. When connected, the :meth:`SSLSocket.cipher` method of SSL sockets will return details about the negotiated cipher. -.. method:: SSLContext.set_ciphersuites(ciphersuites) +.. method:: SSLContext.set_ciphersuites(ciphersuites, /) Set the allowed ciphers for sockets created with this context when connecting using TLS 1.3. The *ciphersuites* argument should be a @@ -1712,7 +1751,7 @@ to speed up repeated connections from the same clients. .. versionadded:: next -.. method:: SSLContext.set_groups(groups) +.. method:: SSLContext.set_groups(groups, /) Set the groups allowed for key agreement for sockets created with this context. It should be a string in the `OpenSSL group list format @@ -1725,7 +1764,36 @@ to speed up repeated connections from the same clients. .. versionadded:: next -.. method:: SSLContext.set_alpn_protocols(protocols) +.. method:: SSLContext.set_client_sigalgs(sigalgs, /) + + Set the signature algorithms allowed for certificate-based client + authentication. It should be a string in the `OpenSSL client sigalgs + list format + `_. + + .. note:: + + When connected, the :meth:`SSLSocket.client_sigalg` method of SSL + sockets will return the signature algorithm used for performing + certificate-based client authentication on that connection. + + .. versionadded:: next + +.. method:: SSLContext.set_server_sigalgs(sigalgs, /) + + Set the signature algorithms allowed for the server to complete the TLS + handshake. It should be a string in the `OpenSSL sigalgs list format + `_. + + .. note:: + + When connected, the :meth:`SSLSocket.server_sigalg` method of SSL + sockets will return the signature algorithm used by the server to + complete the TLS handshake on that connection. + + .. versionadded:: next + +.. method:: SSLContext.set_alpn_protocols(alpn_protocols) Specify which protocols the socket should advertise during the SSL/TLS handshake. It should be a list of ASCII strings, like ``['http/1.1', @@ -1739,7 +1807,7 @@ to speed up repeated connections from the same clients. .. versionadded:: 3.5 -.. method:: SSLContext.set_npn_protocols(protocols) +.. method:: SSLContext.set_npn_protocols(npn_protocols) Specify which protocols the socket should advertise during the SSL/TLS handshake. It should be a list of strings, like ``['http/1.1', 'spdy/2']``, @@ -1806,7 +1874,7 @@ to speed up repeated connections from the same clients. .. versionadded:: 3.7 -.. attribute:: SSLContext.set_servername_callback(server_name_callback) +.. method:: SSLContext.set_servername_callback(server_name_callback) This is a legacy API retained for backwards compatibility. When possible, you should use :attr:`sni_callback` instead. The given *server_name_callback* @@ -1820,7 +1888,7 @@ to speed up repeated connections from the same clients. .. versionadded:: 3.4 -.. method:: SSLContext.load_dh_params(dhfile) +.. method:: SSLContext.load_dh_params(dhfile, /) Load the key generation parameters for Diffie-Hellman (DH) key exchange. Using DH key exchange improves forward secrecy at the expense of @@ -1833,7 +1901,7 @@ to speed up repeated connections from the same clients. .. versionadded:: 3.3 -.. method:: SSLContext.set_ecdh_curve(curve_name) +.. method:: SSLContext.set_ecdh_curve(curve_name, /) Set the curve name for Elliptic Curve-based Diffie-Hellman (ECDH) key exchange. ECDH is significantly faster than regular DH while arguably @@ -2707,12 +2775,12 @@ purpose. It wraps an OpenSSL memory BIO (Basic IO) object: A boolean indicating whether the memory BIO is current at the end-of-file position. - .. method:: MemoryBIO.read(n=-1) + .. method:: MemoryBIO.read(n=-1, /) Read up to *n* bytes from the memory buffer. If *n* is not specified or negative, all bytes are returned. - .. method:: MemoryBIO.write(buf) + .. method:: MemoryBIO.write(buf, /) Write the bytes from *buf* to the memory BIO. The *buf* argument must be an object supporting the buffer protocol. @@ -2876,7 +2944,7 @@ of TLS/SSL. Some new TLS 1.3 features are not yet available. process certificate requests while they send or receive application data from the server. - TLS 1.3 features like early data, deferred TLS client cert request, - signature algorithm configuration, and rekeying are not supported yet. + and rekeying are not supported yet. .. seealso:: diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 853882ebec58af..5462c5dc425e2e 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -88,7 +88,7 @@ and improvements in user-friendliness and correctness. * :ref:`PEP 741: Python configuration C API ` * :ref:`PEP 750: Template strings ` * :ref:`PEP 758: Allow except and except* expressions without parentheses ` -* :ref:`PEP 761: Discontinuation of PGP signatures ` +* :ref:`PEP 761: Discontinuation of PGP signatures ` * :ref:`PEP 765: Disallow return/break/continue that exit a finally block ` * :ref:`Free-threaded mode improvements ` * :ref:`PEP 768: Safe external debugger interface for CPython ` @@ -2820,29 +2820,6 @@ CPython bytecode changes (Contributed by Irit Katriel in :gh:`100239`.) -Build changes -============= - -* GNU Autoconf 2.72 is now required to generate :file:`configure`. - (Contributed by Erlend Aasland in :gh:`115765`.) - -* ``#pragma``-based linking with ``python3*.lib`` can now be switched off - with :c:expr:`Py_NO_LINK_LIB`. (Contributed by Jean-Christophe - Fillion-Robin in :gh:`82909`.) - -.. _whatsnew314-pep761: - -PEP 761: Discontinuation of PGP signatures ------------------------------------------- - -PGP signatures will not be available for CPython 3.14 and onwards. -Users verifying artifacts must use `Sigstore verification materials`_ for -verifying CPython artifacts. This change in release process is specified -in :pep:`761`. - -.. _Sigstore verification materials: https://www.python.org/downloads/metadata/sigstore/ - - C API changes ============= @@ -3120,6 +3097,72 @@ Removed (Removed in :gh:`133079`, see also :gh:`130396`.) +Build Changes +============= + +* GNU Autoconf 2.72 is now required to generate :file:`configure`. + (Contributed by Erlend Aasland in :gh:`115765`.) + +* ``wasm32-unknown-emscripten`` is now a :pep:`11` tier 3 platform. + (Contributed by R. Hood Chatham in :gh:`127146`, :gh:`127683`, and :gh:`136931`.) + +* ``#pragma``-based linking with ``python3*.lib`` can now be switched off + with :c:expr:`Py_NO_LINK_LIB`. + (Contributed by Jean-Christophe Fillion-Robin in :gh:`82909`.) + +* CPython now enables a set of recommended compiler options by default + for improved security. + Use the :option:`--disable-safety` :file:`configure` option to disable them, + or the :option:`--enable-slower-safety` option for a larger set + of compiler options, albeit with a performance cost. + +* The ``WITH_FREELISTS`` macro and ``--without-freelists`` :file:`configure` + option have been removed. + +* The new :file:`configure` option :option:`--with-tail-call-interp` + may be used to enable the experimental tail call interpreter. + See :ref:`whatsnew314-tail-call` for further details. + +* To disable the new remote debugging support, use the + :option:`--without-remote-debug` :file:`configure` option. + This may be useful for security reasons. + +.. _whatsnew314-build_details: + +:file:`build-details.json` +-------------------------- + +Installations of Python now contain a new file, :file:`build-details.json`. +This is a static JSON document containing build details for CPython, +to allow for introspection without needing to run code. +This is helpful for use-cases such as Python launchers, cross-compilation, +and so on. + +:file:`build-details.json` must be installed in the platform-independent +standard library directory. This corresponds to the :ref:`'stdlib' +` :mod:`sysconfig` installation path, +which can be found by running ``sysconfig.get_path('stdlib')``. + +.. seealso:: + :pep:`739` -- ``build-details.json`` 1.0 -- a static description file + for Python build details + +.. _whatsnew314-no-more-pgp: + +Discontinuation of PGP signatures +--------------------------------- + +PGP (Pretty Good Privacy) signatures will not be provided +for releases of Python 3.14 or future versions. +To verify CPython artifacts, users must use `Sigstore verification materials +`__. +Releases have been signed using Sigstore_ since Python 3.11. + +This change in release process was specified in :pep:`761`. + +.. _Sigstore: https://www.sigstore.dev/ + + Porting to Python 3.14 ====================== diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 3236213de5aaa2..b98ee202ec6db6 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -442,6 +442,23 @@ ssl connection is made. (Contributed by Ron Frederick in :gh:`137197`.) +* Added new methods for managing signature algorithms: + + * :func:`ssl.get_sigalgs` returns a list of all available TLS signature + algorithms. This call requires OpenSSL 3.4 or later. + * :meth:`ssl.SSLContext.set_client_sigalgs` sets the signature algorithms + allowed for certificate-based client authentication. + * :meth:`ssl.SSLContext.set_server_sigalgs` sets the signature algorithms + allowed for the server to complete the TLS handshake. + * :meth:`ssl.SSLSocket.client_sigalg` returns the signature algorithm + selected for client authentication on the current connection. This call + requires OpenSSL 3.5 or later. + * :meth:`ssl.SSLSocket.server_sigalg` returns the signature algorithm + selected for the server to complete the TLS handshake on the current + connection. This call requires OpenSSL 3.5 or later. + + (Contributed by Ron Frederick in :gh:`138252`.) + tarfile ------- diff --git a/Include/modsupport.h b/Include/modsupport.h index 45aea017dfe838..094b9ff0e5ccf8 100644 --- a/Include/modsupport.h +++ b/Include/modsupport.h @@ -56,58 +56,6 @@ PyAPI_FUNC(int) PyModule_ExecDef(PyObject *module, PyModuleDef *def); #define Py_CLEANUP_SUPPORTED 0x20000 -#define PYTHON_API_VERSION 1013 -#define PYTHON_API_STRING "1013" -/* The API version is maintained (independently from the Python version) - so we can detect mismatches between the interpreter and dynamically - loaded modules. These are diagnosed by an error message but - the module is still loaded (because the mismatch can only be tested - after loading the module). The error message is intended to - explain the core dump a few seconds later. - - The symbol PYTHON_API_STRING defines the same value as a string - literal. *** PLEASE MAKE SURE THE DEFINITIONS MATCH. *** - - Please add a line or two to the top of this log for each API - version change: - - 22-Feb-2006 MvL 1013 PEP 353 - long indices for sequence lengths - - 19-Aug-2002 GvR 1012 Changes to string object struct for - interning changes, saving 3 bytes. - - 17-Jul-2001 GvR 1011 Descr-branch, just to be on the safe side - - 25-Jan-2001 FLD 1010 Parameters added to PyCode_New() and - PyFrame_New(); Python 2.1a2 - - 14-Mar-2000 GvR 1009 Unicode API added - - 3-Jan-1999 GvR 1007 Decided to change back! (Don't reuse 1008!) - - 3-Dec-1998 GvR 1008 Python 1.5.2b1 - - 18-Jan-1997 GvR 1007 string interning and other speedups - - 11-Oct-1996 GvR renamed Py_Ellipses to Py_Ellipsis :-( - - 30-Jul-1996 GvR Slice and ellipses syntax added - - 23-Jul-1996 GvR For 1.4 -- better safe than sorry this time :-) - - 7-Nov-1995 GvR Keyword arguments (should've been done at 1.3 :-( ) - - 10-Jan-1995 GvR Renamed globals to new naming scheme - - 9-Jan-1995 GvR Initial version (incompatible with older API) -*/ - -/* The PYTHON_ABI_VERSION is introduced in PEP 384. For the lifetime of - Python 3, it will stay at the value of 3; changes to the limited API - must be performed in a strictly backwards-compatible manner. */ -#define PYTHON_ABI_VERSION 3 -#define PYTHON_ABI_STRING "3" - PyAPI_FUNC(PyObject *) PyModule_Create2(PyModuleDef*, int apiver); #ifdef Py_LIMITED_API @@ -135,7 +83,7 @@ PyAPI_FUNC(PyObject *) PyModule_FromDefAndSpec2(PyModuleDef *def, #endif /* New in 3.5 */ /* ABI info & checking (new in 3.15) */ -#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030f0000 +#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= _Py_PACK_VERSION(3, 15) typedef struct PyABIInfo { uint8_t abiinfo_major_version; uint8_t abiinfo_minor_version; diff --git a/Include/patchlevel.h b/Include/patchlevel.h index 532873b51e65bb..63ebbb32d06fa2 100644 --- a/Include/patchlevel.h +++ b/Include/patchlevel.h @@ -6,6 +6,9 @@ configure.ac must also be changed. There is also (independent) API version information in modsupport.h. + + This header should self-contained; PC/python_ver_rc.h includes it + without the rest of Python.h. */ /* Values for PY_RELEASE_LEVEL */ @@ -46,4 +49,16 @@ // Public Py_PACK_VERSION is declared in pymacro.h; it needs . + +/* The API and ABI versions are left for backwards compatibility. + They've not been updated since 2006 and 2010, respectively. + API/ABI versioning is now tied to the CPython version. + The *_VERSION and *_STRING symbols should define the same value; as + number and string literal respectively. Make sure the definitions match. +*/ +#define PYTHON_API_VERSION 1013 +#define PYTHON_API_STRING "1013" +#define PYTHON_ABI_VERSION 3 +#define PYTHON_ABI_STRING "3" + #endif //_Py_PATCHLEVEL_H diff --git a/Lib/ssl.py b/Lib/ssl.py index 5b8762bcdc25d1..493ea901d93324 100644 --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -13,6 +13,9 @@ Functions: + get_sigalgs -- return a list of all available TLS signature + algorithms (requires OpenSSL 3.4 or later) + cert_time_to_seconds -- convert time string used for certificate notBefore and notAfter functions to integer seconds past the Epoch (the time values @@ -112,6 +115,7 @@ except ImportError: # RAND_egd is not supported on some platforms pass +from _ssl import get_sigalgs from _ssl import ( @@ -935,6 +939,14 @@ def group(self): """Return the currently selected key agreement group name.""" return self._sslobj.group() + def client_sigalg(self): + """Return the selected client authentication signature algorithm.""" + return self._sslobj.client_sigalg() + + def server_sigalg(self): + """Return the selected server handshake signature algorithm.""" + return self._sslobj.server_sigalg() + def shared_ciphers(self): """Return a list of ciphers shared by the client during the handshake or None if this is not a valid server connection. @@ -1222,6 +1234,22 @@ def group(self): else: return self._sslobj.group() + @_sslcopydoc + def client_sigalg(self): + self._checkClosed() + if self._sslobj is None: + return None + else: + return self._sslobj.client_sigalg() + + @_sslcopydoc + def server_sigalg(self): + self._checkClosed() + if self._sslobj is None: + return None + else: + return self._sslobj.server_sigalg() + @_sslcopydoc def shared_ciphers(self): self._checkClosed() diff --git a/Lib/test/test_build_details.py b/Lib/test/test_build_details.py index bc04963f5ad613..ba9afe69ba46e8 100644 --- a/Lib/test/test_build_details.py +++ b/Lib/test/test_build_details.py @@ -1,12 +1,34 @@ +import importlib import json import os +import os.path import sys import sysconfig import string import unittest +from pathlib import Path from test.support import is_android, is_apple_mobile, is_wasm32 +BASE_PATH = Path( + __file__, # Lib/test/test_build_details.py + '..', # Lib/test + '..', # Lib + '..', # +).resolve() +MODULE_PATH = BASE_PATH / 'Tools' / 'build' / 'generate-build-details.py' + +try: + # Import "generate-build-details.py" as "generate_build_details" + spec = importlib.util.spec_from_file_location( + "generate_build_details", MODULE_PATH + ) + generate_build_details = importlib.util.module_from_spec(spec) + sys.modules["generate_build_details"] = generate_build_details + spec.loader.exec_module(generate_build_details) +except (FileNotFoundError, ImportError): + generate_build_details = None + class FormatTestsBase: @property @@ -31,16 +53,15 @@ def key(self, name): value = value[part] return value - def test_parse(self): - self.data - def test_top_level_container(self): self.assertIsInstance(self.data, dict) for key, value in self.data.items(): with self.subTest(key=key): - if key in ('schema_version', 'base_prefix', 'base_interpreter', 'platform'): + if key in ('schema_version', 'base_prefix', 'base_interpreter', + 'platform'): self.assertIsInstance(value, str) - elif key in ('language', 'implementation', 'abi', 'suffixes', 'libpython', 'c_api', 'arbitrary_data'): + elif key in ('language', 'implementation', 'abi', 'suffixes', + 'libpython', 'c_api', 'arbitrary_data'): self.assertIsInstance(value, dict) def test_base_prefix(self): @@ -71,15 +92,20 @@ def test_language_version_info(self): self.assertEqual(len(value), sys.version_info.n_fields) for part_name, part_value in value.items(): with self.subTest(part=part_name): - self.assertEqual(part_value, getattr(sys.version_info, part_name)) + sys_version_value = getattr(sys.version_info, part_name) + self.assertEqual(part_value, sys_version_value) def test_implementation(self): + impl_ver = sys.implementation.version for key, value in self.key('implementation').items(): with self.subTest(part=key): if key == 'version': - self.assertEqual(len(value), len(sys.implementation.version)) + self.assertEqual(len(value), len(impl_ver)) for part_name, part_value in value.items(): - self.assertEqual(getattr(sys.implementation.version, part_name), part_value) + self.assertFalse(isinstance(sys.implementation.version, dict)) + getattr(sys.implementation.version, part_name) + sys_implementation_value = getattr(impl_ver, part_name) + self.assertEqual(sys_implementation_value, part_value) else: self.assertEqual(getattr(sys.implementation, key), value) @@ -99,7 +125,8 @@ class CPythonBuildDetailsTests(unittest.TestCase, FormatTestsBase): def location(self): if sysconfig.is_python_build(): projectdir = sysconfig.get_config_var('projectbase') - with open(os.path.join(projectdir, 'pybuilddir.txt')) as f: + pybuilddir = os.path.join(projectdir, 'pybuilddir.txt') + with open(pybuilddir, encoding='utf-8') as f: dirname = os.path.join(projectdir, f.read()) else: dirname = sysconfig.get_path('stdlib') @@ -107,7 +134,7 @@ def location(self): @property def contents(self): - with open(self.location, 'r') as f: + with open(self.location, 'r', encoding='utf-8') as f: return f.read() @needs_installed_python @@ -147,5 +174,64 @@ def test_c_api(self): self.assertTrue(os.path.exists(os.path.join(value['pkgconfig_path'], f'python-{version}.pc'))) +@unittest.skipIf( + generate_build_details is None, + "Failed to import generate-build-details" +) +@unittest.skipIf(os.name != 'posix', 'Feature only implemented on POSIX right now') +@unittest.skipIf(is_wasm32, 'Feature not available on WebAssembly builds') +class BuildDetailsRelativePathsTests(unittest.TestCase): + @property + def build_details_absolute_paths(self): + data = generate_build_details.generate_data(schema_version='1.0') + return json.loads(json.dumps(data)) + + @property + def build_details_relative_paths(self): + data = self.build_details_absolute_paths + generate_build_details.make_paths_relative(data, config_path=None) + return data + + def test_round_trip(self): + data_abs_path = self.build_details_absolute_paths + data_rel_path = self.build_details_relative_paths + + self.assertEqual(data_abs_path['base_prefix'], + data_rel_path['base_prefix']) + + base_prefix = data_abs_path['base_prefix'] + + top_level_keys = ('base_interpreter',) + for key in top_level_keys: + self.assertEqual(key in data_abs_path, key in data_rel_path) + if key not in data_abs_path: + continue + + abs_rel_path = os.path.join(base_prefix, data_rel_path[key]) + abs_rel_path = os.path.normpath(abs_rel_path) + self.assertEqual(data_abs_path[key], abs_rel_path) + + second_level_keys = ( + ('libpython', 'dynamic'), + ('libpython', 'dynamic_stableabi'), + ('libpython', 'static'), + ('c_api', 'headers'), + ('c_api', 'pkgconfig_path'), + + ) + for part, key in second_level_keys: + self.assertEqual(part in data_abs_path, part in data_rel_path) + if part not in data_abs_path: + continue + self.assertEqual(key in data_abs_path[part], + key in data_rel_path[part]) + if key not in data_abs_path[part]: + continue + + abs_rel_path = os.path.join(base_prefix, data_rel_path[part][key]) + abs_rel_path = os.path.normpath(abs_rel_path) + self.assertEqual(data_abs_path[part][key], abs_rel_path) + + if __name__ == '__main__': unittest.main() diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index b05c7bb059e76d..d6e06445d53179 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -51,6 +51,10 @@ CAN_GET_SELECTED_OPENSSL_GROUP = ssl.OPENSSL_VERSION_INFO >= (3, 2) CAN_IGNORE_UNKNOWN_OPENSSL_GROUPS = ssl.OPENSSL_VERSION_INFO >= (3, 3) CAN_GET_AVAILABLE_OPENSSL_GROUPS = ssl.OPENSSL_VERSION_INFO >= (3, 5) +CAN_GET_AVAILABLE_OPENSSL_SIGALGS = ssl.OPENSSL_VERSION_INFO >= (3, 4) +CAN_SET_CLIENT_SIGALGS = "AWS-LC" not in ssl.OPENSSL_VERSION +CAN_IGNORE_UNKNOWN_OPENSSL_SIGALGS = ssl.OPENSSL_VERSION_INFO >= (3, 3) +CAN_GET_SELECTED_OPENSSL_SIGALG = ssl.OPENSSL_VERSION_INFO >= (3, 5) PY_SSL_DEFAULT_CIPHERS = sysconfig.get_config_var('PY_SSL_DEFAULT_CIPHERS') PROTOCOL_TO_TLS_VERSION = {} @@ -294,7 +298,8 @@ def test_wrap_socket(sock, *, USE_SAME_TEST_CONTEXT = False _TEST_CONTEXT = None -def testing_context(server_cert=SIGNED_CERTFILE, *, server_chain=True): +def testing_context(server_cert=SIGNED_CERTFILE, *, server_chain=True, + client_cert=None): """Create context client_context, server_context, hostname = testing_context() @@ -321,6 +326,10 @@ def testing_context(server_cert=SIGNED_CERTFILE, *, server_chain=True): if server_chain: server_context.load_verify_locations(SIGNING_CA) + if client_cert: + client_context.load_cert_chain(client_cert) + server_context.verify_mode = ssl.CERT_REQUIRED + if USE_SAME_TEST_CONTEXT: if _TEST_CONTEXT is not None: _TEST_CONTEXT = client_context, server_context, hostname @@ -990,6 +999,37 @@ def test_get_groups(self): self.assertNotIn('P-256', ctx.get_groups()) self.assertIn('P-256', ctx.get_groups(include_aliases=True)) + @unittest.skipUnless(CAN_GET_AVAILABLE_OPENSSL_SIGALGS, + "SSL library doesn't support getting sigalgs") + def test_get_sigalgs(self): + self.assertIn('rsa_pss_rsae_sha256', ssl.get_sigalgs()) + + @unittest.skipUnless(CAN_SET_CLIENT_SIGALGS, + "SSL library doesn't support setting client sigalgs") + def test_set_client_sigalgs(self): + ctx = ssl.create_default_context() + + self.assertIsNone(ctx.set_client_sigalgs('rsa_pss_rsae_sha256')) + + self.assertRaises(ssl.SSLError, ctx.set_client_sigalgs, + 'rsa_pss_rsae_sha256:foo') + + # Ignoring unknown sigalgs is only supported since OpenSSL 3.3. + if CAN_IGNORE_UNKNOWN_OPENSSL_SIGALGS: + self.assertIsNone(ctx.set_client_sigalgs('rsa_pss_rsae_sha256:?foo')) + + def test_set_server_sigalgs(self): + ctx = ssl.create_default_context() + + self.assertIsNone(ctx.set_server_sigalgs('rsa_pss_rsae_sha256')) + + self.assertRaises(ssl.SSLError, ctx.set_server_sigalgs, + 'rsa_pss_rsae_sha256:foo') + + # Ignoring unknown sigalgs is only supported since OpenSSL 3.3. + if CAN_IGNORE_UNKNOWN_OPENSSL_SIGALGS: + self.assertIsNone(ctx.set_server_sigalgs('rsa_pss_rsae_sha256:?foo')) + def test_options(self): # Test default SSLContext options ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) @@ -2814,6 +2854,9 @@ def server_params_test(client_context, server_context, indata=b"FOO\n", }) if CAN_GET_SELECTED_OPENSSL_GROUP: stats.update({'group': s.group()}) + if CAN_GET_SELECTED_OPENSSL_SIGALG: + stats.update({'client_sigalg': s.client_sigalg()}) + stats.update({'server_sigalg': s.server_sigalg()}) s.close() stats['server_alpn_protocols'] = server.selected_alpn_protocols stats['server_shared_ciphers'] = server.shared_ciphers @@ -4273,6 +4316,71 @@ def test_groups(self): chatty=True, connectionchatty=True, sni_name=hostname) + @unittest.skipUnless(CAN_SET_CLIENT_SIGALGS, + "SSL library doesn't support setting client sigalgs") + def test_client_sigalgs(self): + # no mutual auth, so cient_sigalg should be None + client_context, server_context, hostname = testing_context() + stats = server_params_test(client_context, server_context, + chatty=True, connectionchatty=True, + sni_name=hostname) + if CAN_GET_SELECTED_OPENSSL_SIGALG: + self.assertIsNone(stats['client_sigalg']) + + # server auto, client rsa_pss_rsae_sha384 + sigalg = "rsa_pss_rsae_sha384" + client_context, server_context, hostname = \ + testing_context(client_cert=SIGNED_CERTFILE) + client_context.set_client_sigalgs(sigalg) + stats = server_params_test(client_context, server_context, + chatty=True, connectionchatty=True, + sni_name=hostname) + if CAN_GET_SELECTED_OPENSSL_SIGALG: + self.assertEqual(stats['client_sigalg'], sigalg) + + @unittest.skipUnless(CAN_SET_CLIENT_SIGALGS, + "SSL library doesn't support setting client sigalgs") + def test_client_sigalgs_mismatch(self): + client_context, server_context, hostname = \ + testing_context(client_cert=SIGNED_CERTFILE) + client_context.set_client_sigalgs("rsa_pss_rsae_sha256") + server_context.set_client_sigalgs("rsa_pss_rsae_sha384") + + # Some systems return ConnectionResetError on handshake failures + with self.assertRaises((ssl.SSLError, ConnectionResetError)): + server_params_test(client_context, server_context, + chatty=True, connectionchatty=True, + sni_name=hostname) + + def test_server_sigalgs(self): + # server rsa_pss_rsae_sha384, client auto + sigalg = "rsa_pss_rsae_sha384" + client_context, server_context, hostname = testing_context() + server_context.set_server_sigalgs(sigalg) + stats = server_params_test(client_context, server_context, + chatty=True, connectionchatty=True, + sni_name=hostname) + if CAN_GET_SELECTED_OPENSSL_SIGALG: + self.assertEqual(stats['server_sigalg'], sigalg) + + # server auto, client rsa_pss_rsae_sha384 + client_context, server_context, hostname = testing_context() + client_context.set_server_sigalgs(sigalg) + stats = server_params_test(client_context, server_context, + chatty=True, connectionchatty=True, + sni_name=hostname) + if CAN_GET_SELECTED_OPENSSL_SIGALG: + self.assertEqual(stats['server_sigalg'], sigalg) + + def test_server_sigalgs_mismatch(self): + client_context, server_context, hostname = testing_context() + client_context.set_server_sigalgs("rsa_pss_rsae_sha256") + server_context.set_server_sigalgs("rsa_pss_rsae_sha384") + with self.assertRaises(ssl.SSLError): + server_params_test(client_context, server_context, + chatty=True, connectionchatty=True, + sni_name=hostname) + def test_selected_alpn_protocol(self): # selected_alpn_protocol() is None unless ALPN is used. client_context, server_context, hostname = testing_context() diff --git a/Misc/NEWS.d/next/Library/2025-08-30-17-58-04.gh-issue-138252.CDiEby.rst b/Misc/NEWS.d/next/Library/2025-08-30-17-58-04.gh-issue-138252.CDiEby.rst new file mode 100644 index 00000000000000..95420cea2993d9 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-08-30-17-58-04.gh-issue-138252.CDiEby.rst @@ -0,0 +1,4 @@ +:mod:`ssl`: :class:`~ssl.SSLContext` objects can now set client and server +TLS signature algorithms. If Python has been built with OpenSSL 3.5 or later, +:class:`~ssl.SSLSocket` objects can return the signature algorithms selected +on a connection. diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 048e640283638b..0f1d007e47de2a 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -2200,6 +2200,56 @@ _ssl__SSLSocket_group_impl(PySSLSocket *self) #endif } +static PyObject * +ssl_socket_signame_impl(PySSLSocket *socket, + enum py_ssl_server_or_client self_socket_type) +{ +#if OPENSSL_VERSION_NUMBER >= 0x30500000L + int ret; + const char *sigalg; + + if (socket->ssl == NULL) { + Py_RETURN_NONE; + } + ret = (socket->socket_type == self_socket_type) + ? SSL_get0_signature_name(socket->ssl, &sigalg) + : SSL_get0_peer_signature_name(socket->ssl, &sigalg); + if (ret == 0) { + Py_RETURN_NONE; + } + assert(sigalg != NULL); + return PyUnicode_DecodeFSDefault(sigalg); +#else + PyErr_SetString(PyExc_NotImplementedError, + "Getting sig algorithms requires OpenSSL 3.5 or later."); + return NULL; +#endif +} + +/*[clinic input] +@critical_section +_ssl._SSLSocket.client_sigalg +[clinic start generated code]*/ + +static PyObject * +_ssl__SSLSocket_client_sigalg_impl(PySSLSocket *self) +/*[clinic end generated code: output=499dd7fbf021a47b input=a0d9696b5414c627]*/ +{ + return ssl_socket_signame_impl(self, PY_SSL_CLIENT); +} + +/*[clinic input] +@critical_section +_ssl._SSLSocket.server_sigalg +[clinic start generated code]*/ + +static PyObject * +_ssl__SSLSocket_server_sigalg_impl(PySSLSocket *self) +/*[clinic end generated code: output=c508a766a8e275dc input=9063e562a1e6b946]*/ +{ + return ssl_socket_signame_impl(self, PY_SSL_SERVER); +} + /*[clinic input] @critical_section _ssl._SSLSocket.version @@ -3276,6 +3326,8 @@ static PyMethodDef PySSLMethods[] = { _SSL__SSLSOCKET_GET_CHANNEL_BINDING_METHODDEF _SSL__SSLSOCKET_CIPHER_METHODDEF _SSL__SSLSOCKET_GROUP_METHODDEF + _SSL__SSLSOCKET_CLIENT_SIGALG_METHODDEF + _SSL__SSLSOCKET_SERVER_SIGALG_METHODDEF _SSL__SSLSOCKET_SHARED_CIPHERS_METHODDEF _SSL__SSLSOCKET_VERSION_METHODDEF _SSL__SSLSOCKET_SELECTED_ALPN_PROTOCOL_METHODDEF @@ -3727,7 +3779,6 @@ _ssl__SSLContext_get_groups_impl(PySSLContext *self, int include_aliases) num = sk_OPENSSL_CSTRING_num(groups); result = PyList_New(num); if (result == NULL) { - _setSSLError(get_state_ctx(self), "Can't allocate list", 0, __FILE__, __LINE__); goto error; } @@ -3739,9 +3790,7 @@ _ssl__SSLContext_get_groups_impl(PySSLContext *self, int include_aliases) // Group names are plain ASCII, so there's no chance of a decoding // error here. However, an allocation failure could occur when // constructing the Unicode version of the names. - item = PyUnicode_DecodeASCII(group, strlen(group), "strict"); - if (item == NULL) { - _setSSLError(get_state_ctx(self), "Can't allocate group name", 0, __FILE__, __LINE__); + if ((item = PyUnicode_DecodeFSDefault(group)) == NULL) { goto error; } @@ -3761,6 +3810,49 @@ _ssl__SSLContext_get_groups_impl(PySSLContext *self, int include_aliases) #endif } +/*[clinic input] +@critical_section +_ssl._SSLContext.set_client_sigalgs + sigalgslist: str + / +[clinic start generated code]*/ + +static PyObject * +_ssl__SSLContext_set_client_sigalgs_impl(PySSLContext *self, + const char *sigalgslist) +/*[clinic end generated code: output=f4f5be160a29c7d6 input=500d853ce9fd94ff]*/ +{ +#ifdef OPENSSL_IS_AWSLC + _setSSLError(get_state_ctx(self), "can't set client sigalgs on AWS-LC", 0, __FILE__, __LINE__); + return NULL; +#else + if (!SSL_CTX_set1_client_sigalgs_list(self->ctx, sigalgslist)) { + _setSSLError(get_state_ctx(self), "unrecognized signature algorithm", 0, __FILE__, __LINE__); + return NULL; + } + Py_RETURN_NONE; +#endif +} + +/*[clinic input] +@critical_section +_ssl._SSLContext.set_server_sigalgs + sigalgslist: str + / +[clinic start generated code]*/ + +static PyObject * +_ssl__SSLContext_set_server_sigalgs_impl(PySSLContext *self, + const char *sigalgslist) +/*[clinic end generated code: output=31ecb1d310285644 input=653b752e4f8d801b]*/ +{ + if (!SSL_CTX_set1_sigalgs_list(self->ctx, sigalgslist)) { + _setSSLError(get_state_ctx(self), "unrecognized signature algorithm", 0, __FILE__, __LINE__); + return NULL; + } + Py_RETURN_NONE; +} + static int do_protocol_selection(int alpn, unsigned char **out, unsigned char *outlen, const unsigned char *server_protocols, unsigned int server_protocols_len, @@ -5616,6 +5708,8 @@ static struct PyMethodDef context_methods[] = { _SSL__SSLCONTEXT_SET_CIPHERS_METHODDEF _SSL__SSLCONTEXT_SET_CIPHERSUITES_METHODDEF _SSL__SSLCONTEXT_SET_GROUPS_METHODDEF + _SSL__SSLCONTEXT_SET_CLIENT_SIGALGS_METHODDEF + _SSL__SSLCONTEXT_SET_SERVER_SIGALGS_METHODDEF _SSL__SSLCONTEXT__SET_ALPN_PROTOCOLS_METHODDEF _SSL__SSLCONTEXT_LOAD_CERT_CHAIN_METHODDEF _SSL__SSLCONTEXT_LOAD_DH_PARAMS_METHODDEF @@ -6234,6 +6328,39 @@ _ssl_get_default_verify_paths_impl(PyObject *module) return NULL; } +/*[clinic input] +_ssl.get_sigalgs +[clinic start generated code]*/ + +static PyObject * +_ssl_get_sigalgs_impl(PyObject *module) +/*[clinic end generated code: output=ab0791b63856854b input=d96dd6cefec3f86b]*/ +{ +#if OPENSSL_VERSION_NUMBER >= 0x30400000L + const char *sigalgs; + PyObject *sigalgs_str, *sigalgs_list; + + if ((sigalgs = SSL_get1_builtin_sigalgs(NULL)) == NULL) { + PyErr_NoMemory(); + return NULL; + } + + if ((sigalgs_str = PyUnicode_DecodeFSDefault(sigalgs)) == NULL) { + OPENSSL_free((void *)sigalgs); + return NULL; + } + + OPENSSL_free((void *)sigalgs); + sigalgs_list = PyUnicode_Split(sigalgs_str, _Py_LATIN1_CHR(':'), -1); + Py_DECREF(sigalgs_str); + return sigalgs_list; +#else + PyErr_SetString(PyExc_NotImplementedError, + "Getting signature algorithms requires OpenSSL 3.4 or later."); + return NULL; +#endif +} + static PyObject* asn1obj2py(_sslmodulestate *state, ASN1_OBJECT *obj) { @@ -6637,6 +6764,7 @@ static PyMethodDef PySSL_methods[] = { _SSL_RAND_BYTES_METHODDEF _SSL_RAND_STATUS_METHODDEF _SSL_GET_DEFAULT_VERIFY_PATHS_METHODDEF + _SSL_GET_SIGALGS_METHODDEF _SSL_ENUM_CERTIFICATES_METHODDEF _SSL_ENUM_CRLS_METHODDEF _SSL_TXT2OBJ_METHODDEF diff --git a/Modules/clinic/_ssl.c.h b/Modules/clinic/_ssl.c.h index e8b51c1f1e326d..0e8e4a7c8a73c7 100644 --- a/Modules/clinic/_ssl.c.h +++ b/Modules/clinic/_ssl.c.h @@ -219,6 +219,52 @@ _ssl__SSLSocket_group(PyObject *self, PyObject *Py_UNUSED(ignored)) return return_value; } +PyDoc_STRVAR(_ssl__SSLSocket_client_sigalg__doc__, +"client_sigalg($self, /)\n" +"--\n" +"\n"); + +#define _SSL__SSLSOCKET_CLIENT_SIGALG_METHODDEF \ + {"client_sigalg", (PyCFunction)_ssl__SSLSocket_client_sigalg, METH_NOARGS, _ssl__SSLSocket_client_sigalg__doc__}, + +static PyObject * +_ssl__SSLSocket_client_sigalg_impl(PySSLSocket *self); + +static PyObject * +_ssl__SSLSocket_client_sigalg(PyObject *self, PyObject *Py_UNUSED(ignored)) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _ssl__SSLSocket_client_sigalg_impl((PySSLSocket *)self); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + +PyDoc_STRVAR(_ssl__SSLSocket_server_sigalg__doc__, +"server_sigalg($self, /)\n" +"--\n" +"\n"); + +#define _SSL__SSLSOCKET_SERVER_SIGALG_METHODDEF \ + {"server_sigalg", (PyCFunction)_ssl__SSLSocket_server_sigalg, METH_NOARGS, _ssl__SSLSocket_server_sigalg__doc__}, + +static PyObject * +_ssl__SSLSocket_server_sigalg_impl(PySSLSocket *self); + +static PyObject * +_ssl__SSLSocket_server_sigalg(PyObject *self, PyObject *Py_UNUSED(ignored)) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _ssl__SSLSocket_server_sigalg_impl((PySSLSocket *)self); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + PyDoc_STRVAR(_ssl__SSLSocket_version__doc__, "version($self, /)\n" "--\n" @@ -1136,6 +1182,84 @@ _ssl__SSLContext_get_groups(PyObject *self, PyObject *const *args, Py_ssize_t na return return_value; } +PyDoc_STRVAR(_ssl__SSLContext_set_client_sigalgs__doc__, +"set_client_sigalgs($self, sigalgslist, /)\n" +"--\n" +"\n"); + +#define _SSL__SSLCONTEXT_SET_CLIENT_SIGALGS_METHODDEF \ + {"set_client_sigalgs", (PyCFunction)_ssl__SSLContext_set_client_sigalgs, METH_O, _ssl__SSLContext_set_client_sigalgs__doc__}, + +static PyObject * +_ssl__SSLContext_set_client_sigalgs_impl(PySSLContext *self, + const char *sigalgslist); + +static PyObject * +_ssl__SSLContext_set_client_sigalgs(PyObject *self, PyObject *arg) +{ + PyObject *return_value = NULL; + const char *sigalgslist; + + if (!PyUnicode_Check(arg)) { + _PyArg_BadArgument("set_client_sigalgs", "argument", "str", arg); + goto exit; + } + Py_ssize_t sigalgslist_length; + sigalgslist = PyUnicode_AsUTF8AndSize(arg, &sigalgslist_length); + if (sigalgslist == NULL) { + goto exit; + } + if (strlen(sigalgslist) != (size_t)sigalgslist_length) { + PyErr_SetString(PyExc_ValueError, "embedded null character"); + goto exit; + } + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _ssl__SSLContext_set_client_sigalgs_impl((PySSLContext *)self, sigalgslist); + Py_END_CRITICAL_SECTION(); + +exit: + return return_value; +} + +PyDoc_STRVAR(_ssl__SSLContext_set_server_sigalgs__doc__, +"set_server_sigalgs($self, sigalgslist, /)\n" +"--\n" +"\n"); + +#define _SSL__SSLCONTEXT_SET_SERVER_SIGALGS_METHODDEF \ + {"set_server_sigalgs", (PyCFunction)_ssl__SSLContext_set_server_sigalgs, METH_O, _ssl__SSLContext_set_server_sigalgs__doc__}, + +static PyObject * +_ssl__SSLContext_set_server_sigalgs_impl(PySSLContext *self, + const char *sigalgslist); + +static PyObject * +_ssl__SSLContext_set_server_sigalgs(PyObject *self, PyObject *arg) +{ + PyObject *return_value = NULL; + const char *sigalgslist; + + if (!PyUnicode_Check(arg)) { + _PyArg_BadArgument("set_server_sigalgs", "argument", "str", arg); + goto exit; + } + Py_ssize_t sigalgslist_length; + sigalgslist = PyUnicode_AsUTF8AndSize(arg, &sigalgslist_length); + if (sigalgslist == NULL) { + goto exit; + } + if (strlen(sigalgslist) != (size_t)sigalgslist_length) { + PyErr_SetString(PyExc_ValueError, "embedded null character"); + goto exit; + } + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _ssl__SSLContext_set_server_sigalgs_impl((PySSLContext *)self, sigalgslist); + Py_END_CRITICAL_SECTION(); + +exit: + return return_value; +} + PyDoc_STRVAR(_ssl__SSLContext__set_alpn_protocols__doc__, "_set_alpn_protocols($self, protos, /)\n" "--\n" @@ -2892,6 +3016,23 @@ _ssl_get_default_verify_paths(PyObject *module, PyObject *Py_UNUSED(ignored)) return return_value; } +PyDoc_STRVAR(_ssl_get_sigalgs__doc__, +"get_sigalgs($module, /)\n" +"--\n" +"\n"); + +#define _SSL_GET_SIGALGS_METHODDEF \ + {"get_sigalgs", (PyCFunction)_ssl_get_sigalgs, METH_NOARGS, _ssl_get_sigalgs__doc__}, + +static PyObject * +_ssl_get_sigalgs_impl(PyObject *module); + +static PyObject * +_ssl_get_sigalgs(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + return _ssl_get_sigalgs_impl(module); +} + PyDoc_STRVAR(_ssl_txt2obj__doc__, "txt2obj($module, /, txt, name=False)\n" "--\n" @@ -3181,4 +3322,4 @@ _ssl_enum_crls(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObje #ifndef _SSL_ENUM_CRLS_METHODDEF #define _SSL_ENUM_CRLS_METHODDEF #endif /* !defined(_SSL_ENUM_CRLS_METHODDEF) */ -/*[clinic end generated code: output=4e35d2ea2fc46023 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=5a630a1e83927d47 input=a9049054013a1b77]*/ diff --git a/PC/python_ver_rc.h b/PC/python_ver_rc.h index bb98144cd03f15..70c805d3da8427 100644 --- a/PC/python_ver_rc.h +++ b/PC/python_ver_rc.h @@ -8,7 +8,6 @@ #define PYTHON_COPYRIGHT "Copyright \xA9 2001 Python Software Foundation. Copyright \xA9 2000 BeOpen.com. Copyright \xA9 1995-2001 CNRI. Copyright \xA9 1991-1995 SMC." #define MS_WINDOWS -#include "modsupport.h" #include "patchlevel.h" #ifdef Py_DEBUG # define PYTHON_DEBUG_EXT "_d" diff --git a/Tools/build/generate-build-details.py b/Tools/build/generate-build-details.py index 8cd23e2f54f529..ed9ab2844d250a 100644 --- a/Tools/build/generate-build-details.py +++ b/Tools/build/generate-build-details.py @@ -55,7 +55,7 @@ def generate_data(schema_version: str) -> collections.defaultdict[str, Any]: data['language']['version'] = sysconfig.get_python_version() data['language']['version_info'] = version_info_to_dict(sys.version_info) - data['implementation'] = vars(sys.implementation) + data['implementation'] = vars(sys.implementation).copy() data['implementation']['version'] = version_info_to_dict(sys.implementation.version) # Fix cross-compilation if '_multiarch' in data['implementation']: @@ -104,7 +104,7 @@ def generate_data(schema_version: str) -> collections.defaultdict[str, Any]: data['abi']['extension_suffix'] = sysconfig.get_config_var('EXT_SUFFIX') # EXTENSION_SUFFIXES has been constant for a long time, and currently we - # don't have a better information source to find the stable ABI suffix. + # don't have a better information source to find the stable ABI suffix. for suffix in importlib.machinery.EXTENSION_SUFFIXES: if suffix.startswith('.abi'): data['abi']['stable_abi_suffix'] = suffix @@ -133,33 +133,51 @@ def generate_data(schema_version: str) -> collections.defaultdict[str, Any]: def make_paths_relative(data: dict[str, Any], config_path: str | None = None) -> None: # Make base_prefix relative to the config_path directory if config_path: - data['base_prefix'] = os.path.relpath(data['base_prefix'], os.path.dirname(config_path)) + data['base_prefix'] = relative_path(data['base_prefix'], + os.path.dirname(config_path)) + base_prefix = data['base_prefix'] + # Update path values to make them relative to base_prefix - PATH_KEYS = [ + PATH_KEYS = ( 'base_interpreter', 'libpython.dynamic', 'libpython.dynamic_stableabi', 'libpython.static', 'c_api.headers', 'c_api.pkgconfig_path', - ] + ) for entry in PATH_KEYS: - parent, _, child = entry.rpartition('.') + *parents, child = entry.split('.') # Get the key container object try: container = data - for part in parent.split('.'): + for part in parents: container = container[part] + if child not in container: + raise KeyError(child) current_path = container[child] except KeyError: continue # Get the relative path - new_path = os.path.relpath(current_path, data['base_prefix']) + new_path = relative_path(current_path, base_prefix) # Join '.' so that the path is formated as './path' instead of 'path' new_path = os.path.join('.', new_path) container[child] = new_path +def relative_path(path: str, base: str) -> str: + if os.name != 'nt': + return os.path.relpath(path, base) + + # There are no relative paths between drives on Windows. + path_drv, _ = os.path.splitdrive(path) + base_drv, _ = os.path.splitdrive(base) + if path_drv.lower() == base_drv.lower(): + return os.path.relpath(path, base) + + return path + + def main() -> None: parser = argparse.ArgumentParser(exit_on_error=False) parser.add_argument('location') @@ -186,8 +204,9 @@ def main() -> None: make_paths_relative(data, args.config_file_path) json_output = json.dumps(data, indent=2) - with open(args.location, 'w') as f: - print(json_output, file=f) + with open(args.location, 'w', encoding='utf-8') as f: + f.write(json_output) + f.write('\n') if __name__ == '__main__':