Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #255 -- Add verification methods to the Connection object #844

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/api/ssl.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.


Expand Down
68 changes: 68 additions & 0 deletions src/OpenSSL/SSL.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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.
Expand Down
77 changes: 77 additions & 0 deletions tests/test_ssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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`
Expand Down Expand Up @@ -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):
"""
Expand Down