From b005ac00aa1a2e0bc13aa751305b9891831d3427 Mon Sep 17 00:00:00 2001 From: Pablo Sole Date: Wed, 5 Jun 2019 00:11:06 -0700 Subject: [PATCH] Fixes #255 -- Add verification methods to the Connection object --- doc/api/ssl.rst | 2 +- src/OpenSSL/SSL.py | 68 ++++++++++++++++++++++++++++++++++++++++ tests/test_ssl.py | 77 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 146 insertions(+), 1 deletion(-) diff --git a/doc/api/ssl.rst b/doc/api/ssl.rst index c678b2807..ffa2d1b5a 100644 --- a/doc/api/ssl.rst +++ b/doc/api/ssl.rst @@ -27,7 +27,7 @@ Context, Connection. VERIFY_PEER VERIFY_FAIL_IF_NO_PEER_CERT - These constants represent the verification mode used by the Context + These constants represent the verification mode used by the Context and Connection object's :py:meth:`set_verify` method. diff --git a/src/OpenSSL/SSL.py b/src/OpenSSL/SSL.py index 506354d9c..117d404aa 100644 --- a/src/OpenSSL/SSL.py +++ b/src/OpenSSL/SSL.py @@ -1562,6 +1562,8 @@ def __init__(self, context, socket=None): _lib.SSL_set_mode(self._ssl, _lib.SSL_MODE_AUTO_RETRY) self._context = context self._app_data = None + self._verify_helper = None + self._verify_callback = None # References to strings used for Next Protocol Negotiation. OpenSSL's # header files suggest that these might get copied at some point, but @@ -1609,6 +1611,8 @@ def __getattr__(self, name): return getattr(self._socket, name) def _raise_ssl_error(self, ssl, result): + if self._verify_helper is not None: + self._verify_helper.raise_if_problem() if self._context._verify_helper is not None: self._context._verify_helper.raise_if_problem() if self._context._npn_advertise_helper is not None: @@ -2497,6 +2501,70 @@ def request_ocsp(self): ) _openssl_assert(rc == 1) + def set_verify(self, mode, callback): + """ + Set the verification flags for this Connection object to *mode* and + specify that *callback* should be used for verification callbacks. + + While a Connection will inherit the verification config from + its Context, it is also possible to change it once the Connection + has been instantiated already. + + :param mode: The verify mode, this should be one of + :const:`VERIFY_NONE` and :const:`VERIFY_PEER`. If + :const:`VERIFY_PEER` is used, *mode* can be OR:ed with + :const:`VERIFY_FAIL_IF_NO_PEER_CERT` and + :const:`VERIFY_CLIENT_ONCE` to further control the behaviour. + :param callback: The Python callback to use. This should take five + arguments: A Connection object, an X509 object, and three integer + variables, which are in turn potential error number, error depth + and return code. *callback* should return True if verification + passes and False otherwise. + :return: None + + See SSL_set_verify(3SSL) for further details. + """ + if not isinstance(mode, integer_types): + raise TypeError("mode must be an integer") + + 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 set_verify_depth(self, depth): + """ + Set the maximum depth for the certificate chain verification that shall + be allowed for this Connection object. + + :param depth: An integer specifying the verify depth + :return: None + """ + if not isinstance(depth, integer_types): + raise TypeError("depth must be an integer") + + _lib.SSL_set_verify_depth(self._ssl, depth) + + 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 get_verify_depth(self): + """ + Retrieve the Connection object's verify depth, as set by + :meth:`set_verify_depth`. + + :return: The verify depth + """ + return _lib.SSL_get_verify_depth(self._ssl) + # This is similar to the initialization calls at the end of OpenSSL/crypto.py # but is exercised mostly by the Context initializer. diff --git a/tests/test_ssl.py b/tests/test_ssl.py index 7916d435d..58a3da16c 100644 --- a/tests/test_ssl.py +++ b/tests/test_ssl.py @@ -788,6 +788,16 @@ def test_set_verify_depth_wrong_args(self): with pytest.raises(TypeError): context.set_verify_depth(None) + def test_connection_set_verify_depth_wrong_args(self): + """ + `Connection.set_verify_depth` raises `TypeError` if called with a + non-`int` argument. + """ + context = Context(TLSv1_METHOD) + connection = Connection(context, None) + with pytest.raises(TypeError): + connection.set_verify_depth(None) + def test_verify_depth(self): """ `Context.set_verify_depth` sets the number of certificates in @@ -798,6 +808,17 @@ def test_verify_depth(self): context.set_verify_depth(11) assert context.get_verify_depth() == 11 + def test_connection_verify_depth(self): + """ + `Connection.set_verify_depth` sets the number of certificates in + a chain to follow before giving up. The value can be retrieved with + `Connection.get_verify_depth`. + """ + context = Context(TLSv1_METHOD) + connection = Connection(context, None) + connection.set_verify_depth(11) + assert connection.get_verify_depth() == 11 + def _write_encrypted_pem(self, passphrase, tmpfile): """ Write a new private key out to a new file, encrypted using the given @@ -1285,6 +1306,50 @@ def verify_callback(*args): assert "silly verify failure" == str(exc.value) + def test_set_verify_callback_in_connection_object(self): + """ + The first argument passed to the verify callback is the + `Connection` instance for which verification is taking place. + """ + serverContext = Context(TLSv1_METHOD) + serverContext.use_privatekey( + load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM)) + serverContext.use_certificate( + load_certificate(FILETYPE_PEM, cleartextCertificatePEM)) + serverConnection = Connection(serverContext, None) + + class VerifyCallback(object): + def callback(self, connection, *args): + self.connection = connection + return 1 + + verify = VerifyCallback() + clientContext = Context(TLSv1_METHOD) + clientConnection = Connection(clientContext, None) + clientConnection.set_verify(VERIFY_PEER, verify.callback) + clientConnection.set_connect_state() + + handshake_in_memory(clientConnection, serverConnection) + + assert verify.connection is clientConnection + + def test_set_verify_wrong_args(self): + context = Context(TLSv1_METHOD) + with pytest.raises(TypeError): + context.set_verify(None, lambda *args: None) + + with pytest.raises(TypeError): + context.set_verify(VERIFY_PEER, None) + + def test_connection_set_verify_wrong_args(self): + context = Context(TLSv1_METHOD) + connection = Connection(context, None) + with pytest.raises(TypeError): + connection.set_verify(None, lambda *args: None) + + with pytest.raises(TypeError): + connection.set_verify(VERIFY_PEER, None) + def test_add_extra_chain_cert(self, tmpdir): """ `Context.add_extra_chain_cert` accepts an `X509` @@ -1418,6 +1483,18 @@ def test_set_verify_mode(self): VERIFY_PEER | VERIFY_CLIENT_ONCE, lambda *args: None) assert context.get_verify_mode() == (VERIFY_PEER | VERIFY_CLIENT_ONCE) + def test_connection_get_verify_mode(self): + """ + `Connection.get_verify_mode` returns the verify mode flags previously + passed to `Connection.set_verify`. + """ + context = Context(TLSv1_METHOD) + conn = Connection(context, None) + assert conn.get_verify_mode() == 0 + conn.set_verify( + VERIFY_PEER | VERIFY_CLIENT_ONCE, lambda *args: None) + assert conn.get_verify_mode() == (VERIFY_PEER | VERIFY_CLIENT_ONCE) + @pytest.mark.parametrize('mode', [None, 1.0, object(), 'mode']) def test_set_verify_wrong_mode_arg(self, mode): """