diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b27ad002c..942261e04 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -20,6 +20,9 @@ Deprecations: Changes: ^^^^^^^^ +- Add ``OpenSSL.SSL.Connection.set_verify`` and ``OpenSSL.SSL.Connection.get_verify_mode`` + to override the context object's verification flags. + `#1073 `_ - Expose wrappers for some `DTLS `_ primitives. `#1026 `_ diff --git a/setup.py b/setup.py index ecc23cca3..9d611680d 100755 --- a/setup.py +++ b/setup.py @@ -93,7 +93,7 @@ def find_meta(meta): package_dir={"": "src"}, install_requires=[ # Fix cryptographyMinimum in tox.ini when changing this! - "cryptography>=35.0", + "cryptography>=37.0", ], extras_require={ "test": ["flaky", "pretend", "pytest>=3.0.1"], diff --git a/src/OpenSSL/SSL.py b/src/OpenSSL/SSL.py index 12374b707..0f721dfc3 100644 --- a/src/OpenSSL/SSL.py +++ b/src/OpenSSL/SSL.py @@ -1736,6 +1736,35 @@ def get_servername(self): return _ffi.string(name) + def set_verify(self, mode, callback=None): + """ + Override the Context object's verification flags for this specific + connection. See :py:meth:`Context.set_verify` for details. + """ + if not isinstance(mode, int): + raise TypeError("mode must be an integer") + + if callback is None: + self._verify_helper = None + self._verify_callback = None + _lib.SSL_set_verify(self._ssl, mode, _ffi.NULL) + else: + if not callable(callback): + raise TypeError("callback must be callable") + + self._verify_helper = _VerifyHelper(callback) + self._verify_callback = self._verify_helper.callback + _lib.SSL_set_verify(self._ssl, mode, self._verify_callback) + + def get_verify_mode(self): + """ + Retrieve the Connection object's verify mode, as set by + :meth:`set_verify`. + + :return: The verify mode + """ + return _lib.SSL_get_verify_mode(self._ssl) + def set_ciphertext_mtu(self, mtu): """ For DTLS, set the maximum UDP payload size (*not* including IP/UDP diff --git a/tests/test_ssl.py b/tests/test_ssl.py index ccc8a3870..7d5ed5112 100644 --- a/tests/test_ssl.py +++ b/tests/test_ssl.py @@ -2629,6 +2629,51 @@ def test_get_verified_chain_unconnected(self): server = Connection(ctx, None) assert None is server.get_verified_chain() + def test_set_verify_overrides_context(self): + context = Context(SSLv23_METHOD) + context.set_verify(VERIFY_PEER) + conn = Connection(context, None) + conn.set_verify(VERIFY_NONE) + + assert context.get_verify_mode() == VERIFY_PEER + assert conn.get_verify_mode() == VERIFY_NONE + + with pytest.raises(TypeError): + conn.set_verify(None) + + with pytest.raises(TypeError): + conn.set_verify(VERIFY_PEER, "not a callable") + + def test_set_verify_callback_reference(self): + """ + The callback for certificate verification should only be forgotten if the context and all connections + created by it do not use it anymore. + """ + def callback(conn, cert, errnum, depth, ok): # pragma: no cover + return ok + + tracker = ref(callback) + + context = Context(SSLv23_METHOD) + context.set_verify(VERIFY_PEER, callback) + del callback + + conn = Connection(context, None) + context.set_verify(VERIFY_NONE) + + collect() + collect() + assert tracker() + + conn.set_verify(VERIFY_PEER, lambda conn, cert, errnum, depth, ok: ok) + collect() + collect() + callback = tracker() + if callback is not None: + referrers = get_referrers(callback) + if len(referrers) > 1: # pragma: nocover + pytest.fail("Some references remain: %r" % (referrers,)) + def test_get_session_unconnected(self): """ `Connection.get_session` returns `None` when used with an object diff --git a/tox.ini b/tox.ini index 5883e7ade..8edf55d31 100644 --- a/tox.ini +++ b/tox.ini @@ -10,7 +10,7 @@ extras = deps = coverage>=4.2 cryptographyMain: git+https://github.com/pyca/cryptography.git - cryptographyMinimum: cryptography==35.0 + cryptographyMinimum: cryptography==37.0 randomorder: pytest-randomly setenv = # Do not allow the executing environment to pollute the test environment