Skip to content

Commit d0e88a1

Browse files
gh-157513: Add structured version info for ssl
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) <noreply@anthropic.com>
1 parent f815f74 commit d0e88a1

7 files changed

Lines changed: 112 additions & 14 deletions

File tree

Doc/library/ssl.rst

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -982,14 +982,26 @@ Constants
982982

983983
.. data:: OPENSSL_VERSION_INFO
984984

985-
A tuple of five integers representing version information about the
986-
OpenSSL library::
985+
A named tuple of five integers representing version information about the
986+
OpenSSL library loaded by the interpreter:
987+
*major*, *minor*, *fix*, *patch* and *status*::
987988

988989
>>> ssl.OPENSSL_VERSION_INFO
989-
(1, 0, 2, 11, 15)
990+
ssl.OPENSSL_VERSION_INFO(major=3, minor=0, fix=0, patch=13, status=0)
990991

991992
.. versionadded:: 3.2
992993

994+
.. versionchanged:: next
995+
It is now a named tuple.
996+
997+
.. data:: OPENSSL_API_VERSION_INFO
998+
999+
A named tuple containing the version of the OpenSSL library that was used
1000+
for building the module, with the same fields as :const:`OPENSSL_VERSION_INFO`.
1001+
This may be different from the OpenSSL library actually used at runtime.
1002+
1003+
.. versionadded:: next
1004+
9931005
.. data:: OPENSSL_VERSION_NUMBER
9941006

9951007
The raw version number of the OpenSSL library, as a single integer::

Doc/whatsnew/3.16.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,16 @@ sqlite3
559559
(Contributed by Jiseok CHOI in :gh:`150449`.)
560560

561561

562+
ssl
563+
---
564+
565+
* :const:`ssl.OPENSSL_VERSION_INFO` is now a named tuple.
566+
Added :const:`~ssl.OPENSSL_API_VERSION_INFO` which provides information
567+
about the version of the OpenSSL library that was used for building
568+
the module.
569+
(Contributed by Serhiy Storchaka in :gh:`157513`.)
570+
571+
562572
symtable
563573
--------
564574

Lib/ssl.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@
103103
import _ssl # if we can't import it, let the error propagate
104104

105105
from _ssl import OPENSSL_VERSION_NUMBER, OPENSSL_VERSION_INFO, OPENSSL_VERSION
106+
from _ssl import OPENSSL_API_VERSION_INFO
106107
from _ssl import _SSLContext, MemoryBIO, SSLSession
107108
from _ssl import (
108109
SSLError, SSLZeroReturnError, SSLWantReadError, SSLWantWriteError,

Lib/test/pythoninfo.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,7 @@ def format_attr(attr, value):
646646
attributes = (
647647
'OPENSSL_VERSION',
648648
'OPENSSL_VERSION_INFO',
649+
'OPENSSL_API_VERSION_INFO',
649650
'HAS_SNI',
650651
'OP_ALL',
651652
'OP_NO_TLSv1_1',

Lib/test/test_ssl.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,7 @@ def test_parse_cert_CVE_2013_4238(self):
543543
(('emailAddress', 'python-dev@python.org'),))
544544
self.assertEqual(p['subject'], subject)
545545
self.assertEqual(p['issuer'], subject)
546-
if ssl._OPENSSL_API_VERSION >= (0, 9, 8):
546+
if ssl.OPENSSL_API_VERSION_INFO >= (0, 9, 8):
547547
san = (('DNS', 'altnull.python.org\x00example.com'),
548548
('email', 'null@python.org\x00user@example.org'),
549549
('URI', 'http://null.python.org\x00http://example.org'),
@@ -599,6 +599,14 @@ def test_openssl_version(self):
599599
self.assertIsInstance(n, int)
600600
self.assertIsInstance(t, tuple)
601601
self.assertIsInstance(s, str)
602+
self.assertEqual(len(t), 5)
603+
self.assertEqual(t, (t.major, t.minor, t.fix, t.patch, t.status))
604+
a = ssl.OPENSSL_API_VERSION_INFO
605+
self.assertIsInstance(a, tuple)
606+
self.assertEqual(len(a), 5)
607+
self.assertEqual(a, (a.major, a.minor, a.fix, a.patch, a.status))
608+
self.assertIs(ssl._OPENSSL_API_VERSION, a)
609+
self.assertEqual(a.major, t.major)
602610
# Some sanity checks follow
603611
# >= 1.1.1
604612
self.assertGreaterEqual(n, 0x10101000)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:const:`ssl.OPENSSL_VERSION_INFO` is now a named tuple.
2+
Add :const:`ssl.OPENSSL_API_VERSION_INFO` which provides information about
3+
the version of the OpenSSL library that was used for building the module.

Modules/_ssl.c

Lines changed: 73 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7242,12 +7242,62 @@ parse_openssl_version(unsigned long libver,
72427242
*major = libver & 0xFF;
72437243
}
72447244

7245+
PyDoc_STRVAR(openssl_version_info__doc__,
7246+
"ssl.OPENSSL_VERSION_INFO\n\
7247+
\n\
7248+
OpenSSL version information as a named tuple.");
7249+
7250+
static PyStructSequence_Field openssl_version_info_fields[] = {
7251+
{"major", "Major release number"},
7252+
{"minor", "Minor release number"},
7253+
{"fix", "Fix release number"},
7254+
{"patch", "Patch release number"},
7255+
{"status", "Release status"},
7256+
{0}
7257+
};
7258+
7259+
static PyStructSequence_Desc openssl_version_info_desc = {
7260+
"ssl.OPENSSL_VERSION_INFO", /* name */
7261+
openssl_version_info__doc__, /* doc */
7262+
openssl_version_info_fields, /* fields */
7263+
5
7264+
};
7265+
7266+
static PyObject *
7267+
make_openssl_version_info(PyTypeObject *type, unsigned long libver)
7268+
{
7269+
PyObject *version;
7270+
int pos = 0;
7271+
unsigned int major, minor, fix, patch, status;
7272+
7273+
parse_openssl_version(libver, &major, &minor, &fix, &patch, &status);
7274+
version = PyStructSequence_New(type);
7275+
if (version == NULL) {
7276+
return NULL;
7277+
}
7278+
7279+
#define SetItem(VALUE) \
7280+
PyStructSequence_SET_ITEM(version, pos++, VALUE); \
7281+
if (PyErr_Occurred()) { \
7282+
Py_DECREF(version); \
7283+
return NULL; \
7284+
}
7285+
7286+
SetItem(PyLong_FromUnsignedLong(major))
7287+
SetItem(PyLong_FromUnsignedLong(minor))
7288+
SetItem(PyLong_FromUnsignedLong(fix))
7289+
SetItem(PyLong_FromUnsignedLong(patch))
7290+
SetItem(PyLong_FromUnsignedLong(status))
7291+
#undef SetItem
7292+
7293+
return version;
7294+
}
7295+
72457296
static int
72467297
sslmodule_init_versioninfo(PyObject *m)
72477298
{
72487299
PyObject *r;
72497300
unsigned long libver;
7250-
unsigned int major, minor, fix, patch, status;
72517301

72527302
/* OpenSSL version */
72537303
/* SSLeay() gives us the version of the library linked against,
@@ -7258,20 +7308,33 @@ sslmodule_init_versioninfo(PyObject *m)
72587308
if (PyModule_Add(m, "OPENSSL_VERSION_NUMBER", r) < 0)
72597309
return -1;
72607310

7261-
parse_openssl_version(libver, &major, &minor, &fix, &patch, &status);
7262-
r = Py_BuildValue("IIIII", major, minor, fix, patch, status);
7263-
if (PyModule_Add(m, "OPENSSL_VERSION_INFO", r) < 0)
7264-
return -1;
7265-
72667311
r = PyUnicode_FromString(OpenSSL_version(OPENSSL_VERSION));
72677312
if (PyModule_Add(m, "OPENSSL_VERSION", r) < 0)
72687313
return -1;
72697314

7270-
libver = OPENSSL_VERSION_NUMBER;
7271-
parse_openssl_version(libver, &major, &minor, &fix, &patch, &status);
7272-
r = Py_BuildValue("IIIII", major, minor, fix, patch, status);
7273-
if (PyModule_Add(m, "_OPENSSL_API_VERSION", r) < 0)
7315+
PyTypeObject *version_type;
7316+
version_type = PyStructSequence_NewType(&openssl_version_info_desc);
7317+
if (version_type == NULL) {
7318+
return -1;
7319+
}
7320+
if (PyModule_Add(m, "OPENSSL_VERSION_INFO",
7321+
make_openssl_version_info(version_type, libver)) < 0)
7322+
{
7323+
Py_DECREF(version_type);
7324+
return -1;
7325+
}
7326+
r = make_openssl_version_info(version_type, OPENSSL_VERSION_NUMBER);
7327+
Py_DECREF(version_type);
7328+
if (r == NULL) {
72747329
return -1;
7330+
}
7331+
if (PyModule_AddObjectRef(m, "OPENSSL_API_VERSION_INFO", r) < 0 ||
7332+
PyModule_AddObjectRef(m, "_OPENSSL_API_VERSION", r) < 0)
7333+
{
7334+
Py_DECREF(r);
7335+
return -1;
7336+
}
7337+
Py_DECREF(r);
72757338

72767339
return 0;
72777340
}

0 commit comments

Comments
 (0)