https://github.com/python/cpython/commit/3b0c39cb8b03b7f04e6a95d34c6fd1a3430d3884 commit: 3b0c39cb8b03b7f04e6a95d34c6fd1a3430d3884 branch: main author: Serhiy Storchaka <[email protected]> committer: serhiy-storchaka <[email protected]> date: 2026-09-29T15:53:11+03:00 summary:
gh-157513: Add structured version info for ssl (GH-157515) ssl.OPENSSL_VERSION_INFO is now a named tuple. Add ssl.OPENSSL_API_VERSION_INFO which provides information about the version of the OpenSSL library that was used for building the module. The private name _OPENSSL_API_VERSION is kept as an alias. Co-authored-by: Claude Opus 5 (1M context) <[email protected]> files: A Misc/NEWS.d/next/Library/2026-09-14-18-36-31.gh-issue-157513.EW4QOC.rst M Doc/library/ssl.rst M Doc/whatsnew/3.16.rst M Lib/ssl.py M Lib/test/pythoninfo.py M Lib/test/test_ssl.py M Modules/_ssl.c diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index 66fe6c7aee4862..c38763037d67c5 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -982,14 +982,26 @@ Constants .. data:: OPENSSL_VERSION_INFO - A tuple of five integers representing version information about the - OpenSSL library:: + A named tuple of five integers representing version information about the + OpenSSL library loaded by the interpreter: + *major*, *minor*, *fix*, *patch* and *status*:: >>> ssl.OPENSSL_VERSION_INFO - (1, 0, 2, 11, 15) + ssl.OPENSSL_VERSION_INFO(major=3, minor=0, fix=0, patch=13, status=0) .. versionadded:: 3.2 + .. versionchanged:: next + It is now a named tuple. + +.. data:: OPENSSL_API_VERSION_INFO + + A named tuple containing the version of the OpenSSL library that was used + for building the module, with the same fields as :const:`OPENSSL_VERSION_INFO`. + This may be different from the OpenSSL library actually used at runtime. + + .. versionadded:: next + .. data:: OPENSSL_VERSION_NUMBER The raw version number of the OpenSSL library, as a single integer:: diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst index 3808fac14f325c..1253e30ee1d3a8 100644 --- a/Doc/whatsnew/3.16.rst +++ b/Doc/whatsnew/3.16.rst @@ -591,6 +591,16 @@ sqlite3 (Contributed by Serhiy Storchaka in :gh:`157470`.) +ssl +--- + +* :const:`ssl.OPENSSL_VERSION_INFO` is now a named tuple. + Added :const:`~ssl.OPENSSL_API_VERSION_INFO` which provides information + about the version of the OpenSSL library that was used for building + the module. + (Contributed by Serhiy Storchaka in :gh:`157513`.) + + symtable -------- diff --git a/Lib/ssl.py b/Lib/ssl.py index dc957121728f28..44dc0b046f4518 100644 --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -103,6 +103,7 @@ import _ssl # if we can't import it, let the error propagate from _ssl import OPENSSL_VERSION_NUMBER, OPENSSL_VERSION_INFO, OPENSSL_VERSION +from _ssl import OPENSSL_API_VERSION_INFO from _ssl import _SSLContext, MemoryBIO, SSLSession from _ssl import ( SSLError, SSLZeroReturnError, SSLWantReadError, SSLWantWriteError, diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py index 205d6ff7b75b12..54f662c32edfa9 100644 --- a/Lib/test/pythoninfo.py +++ b/Lib/test/pythoninfo.py @@ -632,6 +632,7 @@ def format_attr(attr, value): attributes = ( 'OPENSSL_VERSION', 'OPENSSL_VERSION_INFO', + 'OPENSSL_API_VERSION_INFO', 'HAS_SNI', 'OP_ALL', 'OP_NO_TLSv1_1', diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index abd7710a1d570a..9a6118d94fbbb2 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -545,7 +545,7 @@ def test_parse_cert_CVE_2013_4238(self): (('emailAddress', '[email protected]'),)) self.assertEqual(p['subject'], subject) self.assertEqual(p['issuer'], subject) - if ssl._OPENSSL_API_VERSION >= (0, 9, 8): + if ssl.OPENSSL_API_VERSION_INFO >= (0, 9, 8): san = (('DNS', 'altnull.python.org\x00example.com'), ('email', '[email protected]\[email protected]'), ('URI', 'http://null.python.org\x00http://example.org'), @@ -601,6 +601,14 @@ def test_openssl_version(self): self.assertIsInstance(n, int) self.assertIsInstance(t, tuple) self.assertIsInstance(s, str) + self.assertEqual(len(t), 5) + self.assertEqual(t, (t.major, t.minor, t.fix, t.patch, t.status)) + a = ssl.OPENSSL_API_VERSION_INFO + self.assertIsInstance(a, tuple) + self.assertEqual(len(a), 5) + self.assertEqual(a, (a.major, a.minor, a.fix, a.patch, a.status)) + self.assertIs(ssl._OPENSSL_API_VERSION, a) + self.assertEqual(a.major, t.major) # Some sanity checks follow # >= 1.1.1 self.assertGreaterEqual(n, 0x10101000) diff --git a/Misc/NEWS.d/next/Library/2026-09-14-18-36-31.gh-issue-157513.EW4QOC.rst b/Misc/NEWS.d/next/Library/2026-09-14-18-36-31.gh-issue-157513.EW4QOC.rst new file mode 100644 index 00000000000000..c9d19314a7604d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-09-14-18-36-31.gh-issue-157513.EW4QOC.rst @@ -0,0 +1,3 @@ +:const:`ssl.OPENSSL_VERSION_INFO` is now a named tuple. +Add :const:`ssl.OPENSSL_API_VERSION_INFO` which provides information about +the version of the OpenSSL library that was used for building the module. diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 4fd8bf0ac3b957..360aa3e2454f8b 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -7229,12 +7229,62 @@ parse_openssl_version(unsigned long libver, *major = libver & 0xFF; } +PyDoc_STRVAR(openssl_version_info__doc__, +"ssl.OPENSSL_VERSION_INFO\n\ +\n\ +OpenSSL version information as a named tuple."); + +static PyStructSequence_Field openssl_version_info_fields[] = { + {"major", "Major release number"}, + {"minor", "Minor release number"}, + {"fix", "Fix release number"}, + {"patch", "Patch release number"}, + {"status", "Release status"}, + {0} +}; + +static PyStructSequence_Desc openssl_version_info_desc = { + "ssl.OPENSSL_VERSION_INFO", /* name */ + openssl_version_info__doc__, /* doc */ + openssl_version_info_fields, /* fields */ + 5 +}; + +static PyObject * +make_openssl_version_info(PyTypeObject *type, unsigned long libver) +{ + PyObject *version; + int pos = 0; + unsigned int major, minor, fix, patch, status; + + parse_openssl_version(libver, &major, &minor, &fix, &patch, &status); + version = PyStructSequence_New(type); + if (version == NULL) { + return NULL; + } + +#define SetItem(VALUE) \ + PyStructSequence_SET_ITEM(version, pos++, VALUE); \ + if (PyErr_Occurred()) { \ + Py_DECREF(version); \ + return NULL; \ + } + + SetItem(PyLong_FromUnsignedLong(major)) + SetItem(PyLong_FromUnsignedLong(minor)) + SetItem(PyLong_FromUnsignedLong(fix)) + SetItem(PyLong_FromUnsignedLong(patch)) + SetItem(PyLong_FromUnsignedLong(status)) +#undef SetItem + + return version; +} + static int sslmodule_init_versioninfo(PyObject *m) { PyObject *r; unsigned long libver; - unsigned int major, minor, fix, patch, status; /* OpenSSL version */ /* SSLeay() gives us the version of the library linked against, @@ -7245,20 +7295,33 @@ sslmodule_init_versioninfo(PyObject *m) if (PyModule_Add(m, "OPENSSL_VERSION_NUMBER", r) < 0) return -1; - parse_openssl_version(libver, &major, &minor, &fix, &patch, &status); - r = Py_BuildValue("IIIII", major, minor, fix, patch, status); - if (PyModule_Add(m, "OPENSSL_VERSION_INFO", r) < 0) - return -1; - r = PyUnicode_FromString(OpenSSL_version(OPENSSL_VERSION)); if (PyModule_Add(m, "OPENSSL_VERSION", r) < 0) return -1; - libver = OPENSSL_VERSION_NUMBER; - parse_openssl_version(libver, &major, &minor, &fix, &patch, &status); - r = Py_BuildValue("IIIII", major, minor, fix, patch, status); - if (PyModule_Add(m, "_OPENSSL_API_VERSION", r) < 0) + PyTypeObject *version_type; + version_type = PyStructSequence_NewType(&openssl_version_info_desc); + if (version_type == NULL) { + return -1; + } + if (PyModule_Add(m, "OPENSSL_VERSION_INFO", + make_openssl_version_info(version_type, libver)) < 0) + { + Py_DECREF(version_type); + return -1; + } + r = make_openssl_version_info(version_type, OPENSSL_VERSION_NUMBER); + Py_DECREF(version_type); + if (r == NULL) { return -1; + } + if (PyModule_AddObjectRef(m, "OPENSSL_API_VERSION_INFO", r) < 0 || + PyModule_AddObjectRef(m, "_OPENSSL_API_VERSION", r) < 0) + { + Py_DECREF(r); + return -1; + } + Py_DECREF(r); return 0; } _______________________________________________ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3//lists/python-checkins.python.org Member address: [email protected]
