Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions OpenSSL/SSL.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,36 @@ def wrapper(ok, store_ctx):
"int (*)(int, X509_STORE_CTX *)", wrapper)


class _RawVerifyHelper(_CallbackExceptionHelper):
"""
Wrap a callback such that it can be used as a certificate verification
callback without the extra wrapping of ``_VerifyHelper``.
"""
def __init__(self, callback):
_CallbackExceptionHelper.__init__(self)

@wraps(callback)
def wrapper(ok, store_ctx):
index = _lib.SSL_get_ex_data_X509_STORE_CTX_idx()
ssl = _lib.X509_STORE_CTX_get_ex_data(store_ctx, index)
connection = Connection._reverse_mapping[ssl]

try:
result = callback(connection, store_ctx)
except Exception as e:
self._problems.append(e)
return 0
else:
if result:
_lib.X509_STORE_CTX_set_error(store_ctx, _lib.X509_V_OK)
return 1
else:
return 0

self.callback = _ffi.callback(
"int (*)(int, X509_STORE_CTX *)", wrapper)


class _NpnAdvertiseHelper(_CallbackExceptionHelper):
"""
Wrap a callback such that it can be used as an NPN advertisement callback.
Expand Down Expand Up @@ -753,6 +783,33 @@ def set_verify(self, mode, callback):
_lib.SSL_CTX_set_verify(self._context, mode, self._verify_callback)


def set_raw_verify(self, mode, callback):
"""
Set the verify mode and verify callback. Unlike set_verify, this method
does not provide you with any extra help to pull out useful features:
you are exposed directly to OpenSSL objects and functions.

This is an advanced function, and should almost never be used: instead,
use set_verify.

:param mode: The verify mode, this is either VERIFY_NONE or
VERIFY_PEER combined with possible other flags
:param callback: The Python callback to use
:return: None

See SSL_CTX_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 = _RawVerifyHelper(callback)
self._verify_callback = self._verify_helper.callback
_lib.SSL_CTX_set_verify(self._context, mode, self._verify_callback)


def set_verify_depth(self, depth):
"""
Set the verify depth
Expand Down
91 changes: 91 additions & 0 deletions OpenSSL/test/test_ssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -2017,6 +2017,97 @@ def test_alpn_not_implemented(self):



class RawVerifyTests(TestCase, _LoopbackMixin):
"""
Unit tests for the raw verification helper.
"""
def test_raw_verify_success(self):
"""
Test that returning True from the verify helper allows verification to
succeed.
"""
call_args = []
def verify(conn, context):
call_args.append((conn, context))
return True

serverContext = Context(TLSv1_METHOD)
serverContext.use_privatekey(
load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM))
serverContext.use_certificate(
load_certificate(FILETYPE_PEM, cleartextCertificatePEM))
server = Connection(serverContext, None)
server.set_accept_state()

# Create the client
clientContext = Context(TLSv1_METHOD)
clientContext.set_raw_verify(VERIFY_PEER, verify)
client = Connection(clientContext, None)
client.set_connect_state()

self._interactInMemory(client, server)

# The verify function is called once per cert.
self.assertEqual(len(call_args), 2)
# The first argument is the connection object.
self.assertEqual(client, call_args[0][0])


def test_raw_verify_fail(self):
"""
Returning False from the verify helper destroys the connection.
"""
call_args = []
def verify(conn, context):
call_args.append((conn, context))
return False

serverContext = Context(TLSv1_METHOD)
serverContext.use_privatekey(
load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM))
serverContext.use_certificate(
load_certificate(FILETYPE_PEM, cleartextCertificatePEM))
server = Connection(serverContext, None)
server.set_accept_state()

# Create the client
clientContext = Context(TLSv1_METHOD)
clientContext.set_raw_verify(VERIFY_PEER, verify)
client = Connection(clientContext, None)
client.set_connect_state()

self.assertRaises(Error, self._interactInMemory, server, client)

# The verify function is called only once here.
self.assertEqual(len(call_args), 1)


def test_raw_verify_exception(self):
"""
Throwing exceptions from the verify helper destroys the connection.
"""
def verify(conn, context):
raise ValueError("Whoops!")
return True

serverContext = Context(TLSv1_METHOD)
serverContext.use_privatekey(
load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM))
serverContext.use_certificate(
load_certificate(FILETYPE_PEM, cleartextCertificatePEM))
server = Connection(serverContext, None)
server.set_accept_state()

# Create the client
clientContext = Context(TLSv1_METHOD)
clientContext.set_raw_verify(VERIFY_PEER, verify)
client = Connection(clientContext, None)
client.set_connect_state()

self.assertRaises(ValueError, self._interactInMemory, server, client)



class SessionTests(TestCase):
"""
Unit tests for :py:obj:`OpenSSL.SSL.Session`.
Expand Down
20 changes: 20 additions & 0 deletions doc/api/ssl.rst
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,26 @@ Context objects have the following methods:
and false otherwise.


.. py:method:: Context.set_raw_verify(mode, callback)

A version of :py:meth:`set_verify` that provides OpenSSL objects directly
to *callback*, rather than unwrapping them. This is an expert-level
function and should only be used when you know what you're doing: in almost
all cases prefer :py:meth:`set_verify`.

Set the verification flags for this Context object to *mode* and specify
that *callback* should be used for verification callbacks. *mode* should be
one of :py:const:`VERIFY_NONE` and :py:const:`VERIFY_PEER`. If
:py:const:`VERIFY_PEER` is used, *mode* can be OR:ed with
:py:const:`VERIFY_FAIL_IF_NO_PEER_CERT` and :py:const:`VERIFY_CLIENT_ONCE`
to further control the behaviour.

*callback* should take two arguments: A Connection object, and an OpenSSL
X509 Store Context object. This second object can only be manipulated using
functions provided by the ``cryptography`` project. *callback* should
return true if verification passes and false otherwise.


.. py:method:: Context.set_verify_depth(depth)

Set the maximum depth for the certificate chain verification that shall be
Expand Down