Skip to content

Commit ab773d0

Browse files
committed
Attempt to prohibit mutating a Context after its in use
1 parent 9b8c497 commit ab773d0

File tree

2 files changed

+73
-0
lines changed

2 files changed

+73
-0
lines changed

CHANGELOG.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,23 @@ Changelog
44
Versions are year-based with a strict backward-compatibility policy.
55
The third digit is only for regressions.
66

7+
UNRELEASED
8+
----------
9+
10+
Backward-incompatible changes:
11+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
12+
13+
Deprecations:
14+
^^^^^^^^^^^^^
15+
16+
- Attempting using any methods that mutate an ``OpenSSL.SSL.Context`` after it
17+
has been used to create an ``OpenSSL.SSL.Connection`` will emit a warning. In
18+
a future release, this will raise an exception.
19+
20+
Changes:
21+
^^^^^^^^
22+
23+
724
25.0.0 (2025-01-12)
825
-------------------
926

src/OpenSSL/SSL.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -827,6 +827,23 @@ class Session:
827827
_session: Any
828828

829829

830+
def _require_not_used(f):
831+
@wraps(f)
832+
def inner(self, *args, **kwargs):
833+
if self._used:
834+
warnings.warn(
835+
(
836+
"Attempting to mutate a Context after a Connection was "
837+
"created. In the future, this will raise an exception"
838+
),
839+
DeprecationWarning,
840+
stacklevel=2,
841+
)
842+
return f(self, *args, **kwargs)
843+
844+
return inner
845+
846+
830847
class Context:
831848
"""
832849
:class:`OpenSSL.SSL.Context` instances define the parameters for setting
@@ -870,6 +887,7 @@ def __init__(self, method: int) -> None:
870887
context = _ffi.gc(context, _lib.SSL_CTX_free)
871888

872889
self._context = context
890+
self._used = False
873891
self._passphrase_helper: _PassphraseHelper | None = None
874892
self._passphrase_callback: _PassphraseCallback[Any] | None = None
875893
self._passphrase_userdata: Any | None = None
@@ -898,6 +916,7 @@ def __init__(self, method: int) -> None:
898916
self.set_min_proto_version(version)
899917
self.set_max_proto_version(version)
900918

919+
@_require_not_used
901920
def set_min_proto_version(self, version: int) -> None:
902921
"""
903922
Set the minimum supported protocol version. Setting the minimum
@@ -911,6 +930,7 @@ def set_min_proto_version(self, version: int) -> None:
911930
_lib.SSL_CTX_set_min_proto_version(self._context, version) == 1
912931
)
913932

933+
@_require_not_used
914934
def set_max_proto_version(self, version: int) -> None:
915935
"""
916936
Set the maximum supported protocol version. Setting the maximum
@@ -924,6 +944,7 @@ def set_max_proto_version(self, version: int) -> None:
924944
_lib.SSL_CTX_set_max_proto_version(self._context, version) == 1
925945
)
926946

947+
@_require_not_used
927948
def load_verify_locations(
928949
self,
929950
cafile: _StrOrBytesPath | None,
@@ -971,6 +992,7 @@ def wrapper(size: int, verify: bool, userdata: Any) -> bytes:
971992
FILETYPE_PEM, wrapper, more_args=True, truncate=True
972993
)
973994

995+
@_require_not_used
974996
def set_passwd_cb(
975997
self,
976998
callback: _PassphraseCallback[_T],
@@ -1004,6 +1026,7 @@ def set_passwd_cb(
10041026
)
10051027
self._passphrase_userdata = userdata
10061028

1029+
@_require_not_used
10071030
def set_default_verify_paths(self) -> None:
10081031
"""
10091032
Specify that the platform provided CA certificates are to be used for
@@ -1079,6 +1102,7 @@ def _fallback_default_verify_paths(
10791102
self.load_verify_locations(None, capath)
10801103
break
10811104

1105+
@_require_not_used
10821106
def use_certificate_chain_file(self, certfile: _StrOrBytesPath) -> None:
10831107
"""
10841108
Load a certificate chain from a file.
@@ -1096,6 +1120,7 @@ def use_certificate_chain_file(self, certfile: _StrOrBytesPath) -> None:
10961120
if not result:
10971121
_raise_current_error()
10981122

1123+
@_require_not_used
10991124
def use_certificate_file(
11001125
self, certfile: _StrOrBytesPath, filetype: int = FILETYPE_PEM
11011126
) -> None:
@@ -1120,6 +1145,7 @@ def use_certificate_file(
11201145
if not use_result:
11211146
_raise_current_error()
11221147

1148+
@_require_not_used
11231149
def use_certificate(self, cert: X509 | x509.Certificate) -> None:
11241150
"""
11251151
Load a certificate from a X509 object
@@ -1144,6 +1170,7 @@ def use_certificate(self, cert: X509 | x509.Certificate) -> None:
11441170
if not use_result:
11451171
_raise_current_error()
11461172

1173+
@_require_not_used
11471174
def add_extra_chain_cert(self, certobj: X509 | x509.Certificate) -> None:
11481175
"""
11491176
Add certificate to chain
@@ -1176,6 +1203,7 @@ def _raise_passphrase_exception(self) -> None:
11761203

11771204
_raise_current_error()
11781205

1206+
@_require_not_used
11791207
def use_privatekey_file(
11801208
self, keyfile: _StrOrBytesPath, filetype: int = FILETYPE_PEM
11811209
) -> None:
@@ -1200,6 +1228,7 @@ def use_privatekey_file(
12001228
if not use_result:
12011229
self._raise_passphrase_exception()
12021230

1231+
@_require_not_used
12031232
def use_privatekey(self, pkey: _PrivateKey | PKey) -> None:
12041233
"""
12051234
Load a private key from a PKey object
@@ -1234,6 +1263,7 @@ def check_privatekey(self) -> None:
12341263
if not _lib.SSL_CTX_check_private_key(self._context):
12351264
_raise_current_error()
12361265

1266+
@_require_not_used
12371267
def load_client_ca(self, cafile: bytes) -> None:
12381268
"""
12391269
Load the trusted certificates that will be sent to the client. Does
@@ -1249,6 +1279,7 @@ def load_client_ca(self, cafile: bytes) -> None:
12491279
_openssl_assert(ca_list != _ffi.NULL)
12501280
_lib.SSL_CTX_set_client_CA_list(self._context, ca_list)
12511281

1282+
@_require_not_used
12521283
def set_session_id(self, buf: bytes) -> None:
12531284
"""
12541285
Set the session id to *buf* within which a session can be reused for
@@ -1266,6 +1297,7 @@ def set_session_id(self, buf: bytes) -> None:
12661297
== 1
12671298
)
12681299

1300+
@_require_not_used
12691301
def set_session_cache_mode(self, mode: int) -> int:
12701302
"""
12711303
Set the behavior of the session cache used by all connections using
@@ -1293,6 +1325,7 @@ def get_session_cache_mode(self) -> int:
12931325
"""
12941326
return _lib.SSL_CTX_get_session_cache_mode(self._context)
12951327

1328+
@_require_not_used
12961329
def set_verify(
12971330
self, mode: int, callback: _VerifyCallback | None = None
12981331
) -> None:
@@ -1330,6 +1363,7 @@ def set_verify(
13301363
self._verify_callback = self._verify_helper.callback
13311364
_lib.SSL_CTX_set_verify(self._context, mode, self._verify_callback)
13321365

1366+
@_require_not_used
13331367
def set_verify_depth(self, depth: int) -> None:
13341368
"""
13351369
Set the maximum depth for the certificate chain verification that shall
@@ -1361,6 +1395,7 @@ def get_verify_depth(self) -> int:
13611395
"""
13621396
return _lib.SSL_CTX_get_verify_depth(self._context)
13631397

1398+
@_require_not_used
13641399
def load_tmp_dh(self, dhfile: _StrOrBytesPath) -> None:
13651400
"""
13661401
Load parameters for Ephemeral Diffie-Hellman
@@ -1382,6 +1417,7 @@ def load_tmp_dh(self, dhfile: _StrOrBytesPath) -> None:
13821417
res = _lib.SSL_CTX_set_tmp_dh(self._context, dh)
13831418
_openssl_assert(res == 1)
13841419

1420+
@_require_not_used
13851421
def set_tmp_ecdh(self, curve: _EllipticCurve | ec.EllipticCurve) -> None:
13861422
"""
13871423
Select a curve to use for ECDHE key exchange.
@@ -1421,6 +1457,7 @@ def set_tmp_ecdh(self, curve: _EllipticCurve | ec.EllipticCurve) -> None:
14211457
ec = _ffi.gc(ec, _lib.EC_KEY_free)
14221458
_lib.SSL_CTX_set_tmp_ecdh(self._context, ec)
14231459

1460+
@_require_not_used
14241461
def set_cipher_list(self, cipher_list: bytes) -> None:
14251462
"""
14261463
Set the list of ciphers to be used in this context.
@@ -1460,6 +1497,7 @@ def set_cipher_list(self, cipher_list: bytes) -> None:
14601497
],
14611498
)
14621499

1500+
@_require_not_used
14631501
def set_client_ca_list(
14641502
self, certificate_authorities: Sequence[X509Name]
14651503
) -> None:
@@ -1497,6 +1535,7 @@ def set_client_ca_list(
14971535

14981536
_lib.SSL_CTX_set_client_CA_list(self._context, name_stack)
14991537

1538+
@_require_not_used
15001539
def add_client_ca(
15011540
self, certificate_authority: X509 | x509.Certificate
15021541
) -> None:
@@ -1531,6 +1570,7 @@ def add_client_ca(
15311570
)
15321571
_openssl_assert(add_result == 1)
15331572

1573+
@_require_not_used
15341574
def set_timeout(self, timeout: int) -> None:
15351575
"""
15361576
Set the timeout for newly created sessions for this Context object to
@@ -1554,6 +1594,7 @@ def get_timeout(self) -> int:
15541594
"""
15551595
return _lib.SSL_CTX_get_timeout(self._context)
15561596

1597+
@_require_not_used
15571598
def set_info_callback(
15581599
self, callback: Callable[[Connection, int, int], None]
15591600
) -> None:
@@ -1579,6 +1620,7 @@ def wrapper(ssl, where, return_code): # type: ignore[no-untyped-def]
15791620
_lib.SSL_CTX_set_info_callback(self._context, self._info_callback)
15801621

15811622
@_requires_keylog
1623+
@_require_not_used
15821624
def set_keylog_callback(
15831625
self, callback: Callable[[Connection, bytes], None]
15841626
) -> None:
@@ -1613,6 +1655,7 @@ def get_app_data(self) -> Any:
16131655
"""
16141656
return self._app_data
16151657

1658+
@_require_not_used
16161659
def set_app_data(self, data: Any) -> None:
16171660
"""
16181661
Set the application data (will be returned from get_app_data())
@@ -1639,6 +1682,7 @@ def get_cert_store(self) -> X509Store | None:
16391682
pystore._store = store
16401683
return pystore
16411684

1685+
@_require_not_used
16421686
def set_options(self, options: int) -> int:
16431687
"""
16441688
Add options. Options set before are not cleared!
@@ -1652,6 +1696,7 @@ def set_options(self, options: int) -> int:
16521696

16531697
return _lib.SSL_CTX_set_options(self._context, options)
16541698

1699+
@_require_not_used
16551700
def set_mode(self, mode: int) -> int:
16561701
"""
16571702
Add modes via bitmask. Modes set before are not cleared! This method
@@ -1665,6 +1710,7 @@ def set_mode(self, mode: int) -> int:
16651710

16661711
return _lib.SSL_CTX_set_mode(self._context, mode)
16671712

1713+
@_require_not_used
16681714
def set_tlsext_servername_callback(
16691715
self, callback: Callable[[Connection], None]
16701716
) -> None:
@@ -1690,6 +1736,7 @@ def wrapper(ssl, alert, arg): # type: ignore[no-untyped-def]
16901736
self._context, self._tlsext_servername_callback
16911737
)
16921738

1739+
@_require_not_used
16931740
def set_tlsext_use_srtp(self, profiles: bytes) -> None:
16941741
"""
16951742
Enable support for negotiating SRTP keying material.
@@ -1705,6 +1752,7 @@ def set_tlsext_use_srtp(self, profiles: bytes) -> None:
17051752
_lib.SSL_CTX_set_tlsext_use_srtp(self._context, profiles) == 0
17061753
)
17071754

1755+
@_require_not_used
17081756
def set_alpn_protos(self, protos: list[bytes]) -> None:
17091757
"""
17101758
Specify the protocols that the client is prepared to speak after the
@@ -1742,6 +1790,7 @@ def set_alpn_protos(self, protos: list[bytes]) -> None:
17421790
== 0
17431791
)
17441792

1793+
@_require_not_used
17451794
def set_alpn_select_callback(self, callback: _ALPNSelectCallback) -> None:
17461795
"""
17471796
Specify a callback function that will be called on the server when a
@@ -1786,6 +1835,7 @@ def _set_ocsp_callback(
17861835
rc = _lib.SSL_CTX_set_tlsext_status_arg(self._context, self._ocsp_data)
17871836
_openssl_assert(rc == 1)
17881837

1838+
@_require_not_used
17891839
def set_ocsp_server_callback(
17901840
self,
17911841
callback: _OCSPServerCallback[_T],
@@ -1808,6 +1858,7 @@ def set_ocsp_server_callback(
18081858
helper = _OCSPServerCallbackHelper(callback)
18091859
self._set_ocsp_callback(helper, data)
18101860

1861+
@_require_not_used
18111862
def set_ocsp_client_callback(
18121863
self,
18131864
callback: _OCSPClientCallback[_T],
@@ -1832,6 +1883,7 @@ def set_ocsp_client_callback(
18321883
helper = _OCSPClientCallbackHelper(callback)
18331884
self._set_ocsp_callback(helper, data)
18341885

1886+
@_require_not_used
18351887
def set_cookie_generate_callback(
18361888
self, callback: _CookieGenerateCallback
18371889
) -> None:
@@ -1841,6 +1893,7 @@ def set_cookie_generate_callback(
18411893
self._cookie_generate_helper.callback,
18421894
)
18431895

1896+
@_require_not_used
18441897
def set_cookie_verify_callback(
18451898
self, callback: _CookieVerifyCallback
18461899
) -> None:
@@ -1869,6 +1922,8 @@ def __init__(
18691922
if not isinstance(context, Context):
18701923
raise TypeError("context must be a Context instance")
18711924

1925+
context._used = True
1926+
18721927
ssl = _lib.SSL_new(context._context)
18731928
self._ssl = _ffi.gc(ssl, _lib.SSL_free)
18741929
# We set SSL_MODE_AUTO_RETRY to handle situations where OpenSSL returns
@@ -2000,6 +2055,7 @@ def set_context(self, context: Context) -> None:
20002055

20012056
_lib.SSL_set_SSL_CTX(self._ssl, context._context)
20022057
self._context = context
2058+
self._context._used = True
20032059

20042060
def get_servername(self) -> bytes | None:
20052061
"""

0 commit comments

Comments
 (0)