Skip to content

Commit f46314f

Browse files
authored
Add get_group_name to expose SSL_get0_group_name (#1442)
* Add get_group_name to expose SSL_get0_group_name * Update test with consistent ordering * Add decorator for the get group guard flag * ruff format * Skip test if not Cryptography_HAS_SSL_GET0_GROUP_NAME * Add test case for if group returns NULL * Fix test - add guard to check for session * Set cryptography min to 46.0.0 * Drop Python 3.7 support * Reflect in CHANGELOG.rst that Python 3.7 has been dropped * Add comment about guard against segfault
1 parent 1b2d96d commit f46314f

File tree

7 files changed

+82
-7
lines changed

7 files changed

+82
-7
lines changed

.github/workflows/ci.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ jobs:
1111
matrix:
1212
PYTHON:
1313
# Base builds
14-
- {VERSION: "3.7", NOXSESSION: "tests", OS: "ubuntu-22.04"}
1514
- {VERSION: "3.8", NOXSESSION: "tests"}
1615
- {VERSION: "3.9", NOXSESSION: "tests"}
1716
- {VERSION: "3.10", NOXSESSION: "tests"}
@@ -33,7 +32,6 @@ jobs:
3332
- {VERSION: "3.14t-dev", NOXSESSION: "tests-cryptography-main"}
3433
- {VERSION: "pypy-3.11", NOXSESSION: "tests-cryptography-main"}
3534
# -cryptography-minimum
36-
- {VERSION: "3.7", NOXSESSION: "tests-cryptography-minimum", OS: "ubuntu-22.04"}
3735
- {VERSION: "3.8", NOXSESSION: "tests-cryptography-minimum"}
3836
- {VERSION: "3.9", NOXSESSION: "tests-cryptography-minimum"}
3937
- {VERSION: "3.10", NOXSESSION: "tests-cryptography-minimum"}

CHANGELOG.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,17 @@ The third digit is only for regressions.
1010
Backward-incompatible changes:
1111
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1212

13+
- Dropped support for Python 3.7.
14+
- The minimum ``cryptography`` version is now 46.0.0.
15+
1316
Deprecations:
1417
^^^^^^^^^^^^^
1518

1619
Changes:
1720
^^^^^^^^
1821

22+
- Added ``OpenSSL.SSL.Connection.get_group_name`` to determine which group name was negotiated.
23+
1924
25.3.0 (2025-09-16)
2025
-------------------
2126

doc/introduction.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Other OpenSSL wrappers for Python at the time were also limited, though in diffe
1414
Later it was maintained by `Jean-Paul Calderone`_ who among other things managed to make pyOpenSSL a pure Python project which the current maintainers are *very* grateful for.
1515

1616
Over the time the standard library's ``ssl`` module improved, never reaching the completeness of pyOpenSSL's API coverage.
17-
pyOpenSSL remains the only choice for full-featured TLS code in Python versions 3.7+ and PyPy_.
17+
pyOpenSSL remains the only choice for full-featured TLS code in Python versions 3.8+ and PyPy_.
1818

1919

2020
Development

noxfile.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
nox.options.reuse_existing_virtualenvs = True
44
nox.options.default_venv_backend = "uv|virtualenv"
55

6-
MINIMUM_CRYPTOGRAPHY_VERSION = "45.0.7"
6+
MINIMUM_CRYPTOGRAPHY_VERSION = "46.0.0"
77

88

99
@nox.session

setup.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@ def find_meta(meta):
7777
"Operating System :: Microsoft :: Windows",
7878
"Operating System :: POSIX",
7979
"Programming Language :: Python :: 3",
80-
"Programming Language :: Python :: 3.7",
8180
"Programming Language :: Python :: 3.8",
8281
"Programming Language :: Python :: 3.9",
8382
"Programming Language :: Python :: 3.10",
@@ -90,11 +89,11 @@ def find_meta(meta):
9089
"Topic :: Software Development :: Libraries :: Python Modules",
9190
"Topic :: System :: Networking",
9291
],
93-
python_requires=">=3.7",
92+
python_requires=">=3.8",
9493
packages=find_packages(where="src"),
9594
package_dir={"": "src"},
9695
install_requires=[
97-
"cryptography>=45.0.7,<47",
96+
"cryptography>=46.0.0,<47",
9897
(
9998
"typing-extensions>=4.9; "
10099
"python_version < '3.13' and python_version >= '3.8'"

src/OpenSSL/SSL.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -815,6 +815,11 @@ def explode(*args, **kwargs): # type: ignore[no-untyped-def]
815815
getattr(_lib, "Cryptography_HAS_KEYLOG", 0), "Key logging not available"
816816
)
817817

818+
_requires_ssl_get0_group_name = _make_requires(
819+
getattr(_lib, "Cryptography_HAS_SSL_GET0_GROUP_NAME", 0),
820+
"Getting group name is not supported by the linked OpenSSL version",
821+
)
822+
818823

819824
class Session:
820825
"""
@@ -3202,6 +3207,26 @@ def get_selected_srtp_profile(self) -> bytes:
32023207

32033208
return _ffi.string(profile.name)
32043209

3210+
@_requires_ssl_get0_group_name
3211+
def get_group_name(self) -> str | None:
3212+
"""
3213+
Get the name of the negotiated group for the key exchange.
3214+
3215+
:return: A string giving the group name or :data:`None`.
3216+
"""
3217+
# Do not remove this guard.
3218+
# SSL_get0_group_name crashes with a segfault if called without
3219+
# an established connection (should return NULL but doesn't).
3220+
session = _lib.SSL_get_session(self._ssl)
3221+
if session == _ffi.NULL:
3222+
return None
3223+
3224+
group_name = _lib.SSL_get0_group_name(self._ssl)
3225+
if group_name == _ffi.NULL:
3226+
return None
3227+
3228+
return _ffi.string(group_name).decode("utf-8")
3229+
32053230
def request_ocsp(self) -> None:
32063231
"""
32073232
Called to request that the server sends stapled OCSP data, if

tests/test_ssl.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3437,6 +3437,54 @@ def test_get_protocol_version(self) -> None:
34373437

34383438
assert server_protocol_version == client_protocol_version
34393439

3440+
@pytest.mark.skipif(
3441+
not getattr(_lib, "Cryptography_HAS_SSL_GET0_GROUP_NAME", None),
3442+
reason="SSL_get0_group_name unavailable",
3443+
)
3444+
def test_get_group_name_before_connect(self) -> None:
3445+
"""
3446+
`Connection.get_group_name()` returns `None` if no connection
3447+
has been established.
3448+
"""
3449+
ctx = Context(TLS_METHOD)
3450+
conn = Connection(ctx, None)
3451+
assert conn.get_group_name() is None
3452+
3453+
@pytest.mark.skipif(
3454+
not getattr(_lib, "Cryptography_HAS_SSL_GET0_GROUP_NAME", None),
3455+
reason="SSL_get0_group_name unavailable",
3456+
)
3457+
def test_group_name_null_case(
3458+
self, monkeypatch: pytest.MonkeyPatch
3459+
) -> None:
3460+
"""
3461+
`Connection.get_group_name()` returns `None` when SSL_get0_group_name
3462+
returns NULL.
3463+
"""
3464+
monkeypatch.setattr(_lib, "SSL_get0_group_name", lambda ssl: _ffi.NULL)
3465+
3466+
server, client = loopback()
3467+
assert server.get_group_name() is None
3468+
assert client.get_group_name() is None
3469+
3470+
@pytest.mark.skipif(
3471+
not getattr(_lib, "Cryptography_HAS_SSL_GET0_GROUP_NAME", None),
3472+
reason="SSL_get0_group_name unavailable",
3473+
)
3474+
def test_get_group_name(self) -> None:
3475+
"""
3476+
`Connection.get_group_name()` returns a string giving the
3477+
name of the connection's negotiated key exchange group.
3478+
"""
3479+
server, client = loopback()
3480+
server_group_name = server.get_group_name()
3481+
client_group_name = client.get_group_name()
3482+
3483+
assert isinstance(server_group_name, str)
3484+
assert isinstance(client_group_name, str)
3485+
3486+
assert server_group_name == client_group_name
3487+
34403488
def test_wantReadError(self) -> None:
34413489
"""
34423490
`Connection.bio_read` raises `OpenSSL.SSL.WantReadError` if there are

0 commit comments

Comments
 (0)