Skip to content

Commit

Permalink
Fixes #255 -- Add verification methods to the Connection object
Browse files Browse the repository at this point in the history
  • Loading branch information
Pablo Sole committed Jun 5, 2019
1 parent 1265b06 commit b005ac0
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 1 deletion.
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

0 comments on commit b005ac0

Please sign in to comment.