Skip to content

Commit b005ac0

Browse files
author
Pablo Sole
committed
Fixes pyca#255 -- Add verification methods to the Connection object
1 parent 1265b06 commit b005ac0

File tree

3 files changed

+146
-1
lines changed

3 files changed

+146
-1
lines changed

doc/api/ssl.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Context, Connection.
2727
VERIFY_PEER
2828
VERIFY_FAIL_IF_NO_PEER_CERT
2929
30-
These constants represent the verification mode used by the Context
30+
These constants represent the verification mode used by the Context and Connection
3131
object's :py:meth:`set_verify` method.
3232

3333

src/OpenSSL/SSL.py

+68
Original file line numberDiff line numberDiff line change
@@ -1562,6 +1562,8 @@ def __init__(self, context, socket=None):
15621562
_lib.SSL_set_mode(self._ssl, _lib.SSL_MODE_AUTO_RETRY)
15631563
self._context = context
15641564
self._app_data = None
1565+
self._verify_helper = None
1566+
self._verify_callback = None
15651567

15661568
# References to strings used for Next Protocol Negotiation. OpenSSL's
15671569
# header files suggest that these might get copied at some point, but
@@ -1609,6 +1611,8 @@ def __getattr__(self, name):
16091611
return getattr(self._socket, name)
16101612

16111613
def _raise_ssl_error(self, ssl, result):
1614+
if self._verify_helper is not None:
1615+
self._verify_helper.raise_if_problem()
16121616
if self._context._verify_helper is not None:
16131617
self._context._verify_helper.raise_if_problem()
16141618
if self._context._npn_advertise_helper is not None:
@@ -2497,6 +2501,70 @@ def request_ocsp(self):
24972501
)
24982502
_openssl_assert(rc == 1)
24992503

2504+
def set_verify(self, mode, callback):
2505+
"""
2506+
Set the verification flags for this Connection object to *mode* and
2507+
specify that *callback* should be used for verification callbacks.
2508+
2509+
While a Connection will inherit the verification config from
2510+
its Context, it is also possible to change it once the Connection
2511+
has been instantiated already.
2512+
2513+
:param mode: The verify mode, this should be one of
2514+
:const:`VERIFY_NONE` and :const:`VERIFY_PEER`. If
2515+
:const:`VERIFY_PEER` is used, *mode* can be OR:ed with
2516+
:const:`VERIFY_FAIL_IF_NO_PEER_CERT` and
2517+
:const:`VERIFY_CLIENT_ONCE` to further control the behaviour.
2518+
:param callback: The Python callback to use. This should take five
2519+
arguments: A Connection object, an X509 object, and three integer
2520+
variables, which are in turn potential error number, error depth
2521+
and return code. *callback* should return True if verification
2522+
passes and False otherwise.
2523+
:return: None
2524+
2525+
See SSL_set_verify(3SSL) for further details.
2526+
"""
2527+
if not isinstance(mode, integer_types):
2528+
raise TypeError("mode must be an integer")
2529+
2530+
if not callable(callback):
2531+
raise TypeError("callback must be callable")
2532+
2533+
self._verify_helper = _VerifyHelper(callback)
2534+
self._verify_callback = self._verify_helper.callback
2535+
_lib.SSL_set_verify(self._ssl, mode, self._verify_callback)
2536+
2537+
def set_verify_depth(self, depth):
2538+
"""
2539+
Set the maximum depth for the certificate chain verification that shall
2540+
be allowed for this Connection object.
2541+
2542+
:param depth: An integer specifying the verify depth
2543+
:return: None
2544+
"""
2545+
if not isinstance(depth, integer_types):
2546+
raise TypeError("depth must be an integer")
2547+
2548+
_lib.SSL_set_verify_depth(self._ssl, depth)
2549+
2550+
def get_verify_mode(self):
2551+
"""
2552+
Retrieve the Connection object's verify mode, as set by
2553+
:meth:`set_verify`.
2554+
2555+
:return: The verify mode
2556+
"""
2557+
return _lib.SSL_get_verify_mode(self._ssl)
2558+
2559+
def get_verify_depth(self):
2560+
"""
2561+
Retrieve the Connection object's verify depth, as set by
2562+
:meth:`set_verify_depth`.
2563+
2564+
:return: The verify depth
2565+
"""
2566+
return _lib.SSL_get_verify_depth(self._ssl)
2567+
25002568

25012569
# This is similar to the initialization calls at the end of OpenSSL/crypto.py
25022570
# but is exercised mostly by the Context initializer.

tests/test_ssl.py

+77
Original file line numberDiff line numberDiff line change
@@ -788,6 +788,16 @@ def test_set_verify_depth_wrong_args(self):
788788
with pytest.raises(TypeError):
789789
context.set_verify_depth(None)
790790

791+
def test_connection_set_verify_depth_wrong_args(self):
792+
"""
793+
`Connection.set_verify_depth` raises `TypeError` if called with a
794+
non-`int` argument.
795+
"""
796+
context = Context(TLSv1_METHOD)
797+
connection = Connection(context, None)
798+
with pytest.raises(TypeError):
799+
connection.set_verify_depth(None)
800+
791801
def test_verify_depth(self):
792802
"""
793803
`Context.set_verify_depth` sets the number of certificates in
@@ -798,6 +808,17 @@ def test_verify_depth(self):
798808
context.set_verify_depth(11)
799809
assert context.get_verify_depth() == 11
800810

811+
def test_connection_verify_depth(self):
812+
"""
813+
`Connection.set_verify_depth` sets the number of certificates in
814+
a chain to follow before giving up. The value can be retrieved with
815+
`Connection.get_verify_depth`.
816+
"""
817+
context = Context(TLSv1_METHOD)
818+
connection = Connection(context, None)
819+
connection.set_verify_depth(11)
820+
assert connection.get_verify_depth() == 11
821+
801822
def _write_encrypted_pem(self, passphrase, tmpfile):
802823
"""
803824
Write a new private key out to a new file, encrypted using the given
@@ -1285,6 +1306,50 @@ def verify_callback(*args):
12851306

12861307
assert "silly verify failure" == str(exc.value)
12871308

1309+
def test_set_verify_callback_in_connection_object(self):
1310+
"""
1311+
The first argument passed to the verify callback is the
1312+
`Connection` instance for which verification is taking place.
1313+
"""
1314+
serverContext = Context(TLSv1_METHOD)
1315+
serverContext.use_privatekey(
1316+
load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM))
1317+
serverContext.use_certificate(
1318+
load_certificate(FILETYPE_PEM, cleartextCertificatePEM))
1319+
serverConnection = Connection(serverContext, None)
1320+
1321+
class VerifyCallback(object):
1322+
def callback(self, connection, *args):
1323+
self.connection = connection
1324+
return 1
1325+
1326+
verify = VerifyCallback()
1327+
clientContext = Context(TLSv1_METHOD)
1328+
clientConnection = Connection(clientContext, None)
1329+
clientConnection.set_verify(VERIFY_PEER, verify.callback)
1330+
clientConnection.set_connect_state()
1331+
1332+
handshake_in_memory(clientConnection, serverConnection)
1333+
1334+
assert verify.connection is clientConnection
1335+
1336+
def test_set_verify_wrong_args(self):
1337+
context = Context(TLSv1_METHOD)
1338+
with pytest.raises(TypeError):
1339+
context.set_verify(None, lambda *args: None)
1340+
1341+
with pytest.raises(TypeError):
1342+
context.set_verify(VERIFY_PEER, None)
1343+
1344+
def test_connection_set_verify_wrong_args(self):
1345+
context = Context(TLSv1_METHOD)
1346+
connection = Connection(context, None)
1347+
with pytest.raises(TypeError):
1348+
connection.set_verify(None, lambda *args: None)
1349+
1350+
with pytest.raises(TypeError):
1351+
connection.set_verify(VERIFY_PEER, None)
1352+
12881353
def test_add_extra_chain_cert(self, tmpdir):
12891354
"""
12901355
`Context.add_extra_chain_cert` accepts an `X509`
@@ -1418,6 +1483,18 @@ def test_set_verify_mode(self):
14181483
VERIFY_PEER | VERIFY_CLIENT_ONCE, lambda *args: None)
14191484
assert context.get_verify_mode() == (VERIFY_PEER | VERIFY_CLIENT_ONCE)
14201485

1486+
def test_connection_get_verify_mode(self):
1487+
"""
1488+
`Connection.get_verify_mode` returns the verify mode flags previously
1489+
passed to `Connection.set_verify`.
1490+
"""
1491+
context = Context(TLSv1_METHOD)
1492+
conn = Connection(context, None)
1493+
assert conn.get_verify_mode() == 0
1494+
conn.set_verify(
1495+
VERIFY_PEER | VERIFY_CLIENT_ONCE, lambda *args: None)
1496+
assert conn.get_verify_mode() == (VERIFY_PEER | VERIFY_CLIENT_ONCE)
1497+
14211498
@pytest.mark.parametrize('mode', [None, 1.0, object(), 'mode'])
14221499
def test_set_verify_wrong_mode_arg(self, mode):
14231500
"""

0 commit comments

Comments
 (0)