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..91804a472 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..59056edbe 100644 --- a/tests/test_ssl.py +++ b/tests/test_ssl.py @@ -798,6 +798,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 +1296,33 @@ 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_add_extra_chain_cert(self, tmpdir): """ `Context.add_extra_chain_cert` accepts an `X509` @@ -1418,6 +1456,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) + connection = Connection(context, None) + assert connection.get_verify_mode() == 0 + connection.set_verify( + VERIFY_PEER | VERIFY_CLIENT_ONCE, lambda *args: None) + assert connection.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): """