A big chunk of ContextTests passing
diff --git a/OpenSSL/SSL.py b/OpenSSL/SSL.py
index 865533c..6b8d78a 100644
--- a/OpenSSL/SSL.py
+++ b/OpenSSL/SSL.py
@@ -1,8 +1,15 @@
+from functools import wraps
+
from OpenSSL.xSSL import *
from tls.c import api as _api
+from OpenSSL.crypto import (
+ FILETYPE_PEM, _PassphraseHelper, PKey, X509, _raise_current_error)
+
+_unspecified = object()
+
OPENSSL_VERSION_NUMBER = _api.OPENSSL_VERSION_NUMBER
SSLEAY_VERSION = _api.SSLEAY_VERSION
SSLEAY_CFLAGS = _api.SSLEAY_CFLAGS
@@ -37,6 +44,13 @@
SESS_CACHE_NO_INTERNAL_STORE = _api.SSL_SESS_CACHE_NO_INTERNAL_STORE
SESS_CACHE_NO_INTERNAL = _api.SSL_SESS_CACHE_NO_INTERNAL
+
+
+class Error(Exception):
+ pass
+
+
+
def SSLeay_version(type):
"""
Return a string describing the version of OpenSSL in use.
@@ -44,3 +58,434 @@
:param type: One of the SSLEAY_ constants defined in this module.
"""
return _api.string(_api.SSLeay_version(type))
+
+
+
+class Context(object):
+ """
+ :py:obj:`OpenSSL.SSL.Context` instances define the parameters for setting up
+ new SSL connections.
+ """
+ _methods = {
+ # TODO
+ # SSLv2_METHOD: _api.SSLv2_method,
+ SSLv3_METHOD: _api.SSLv3_method,
+ TLSv1_METHOD: _api.TLSv1_method,
+ SSLv23_METHOD: _api.SSLv23_method,
+ }
+
+ def __init__(self, method):
+ """
+ :param method: One of SSLv2_METHOD, SSLv3_METHOD, SSLv23_METHOD, or
+ TLSv1_METHOD.
+ """
+ if not isinstance(method, int):
+ raise TypeError("method must be an integer")
+
+ try:
+ method_func = self._methods[method]
+ except KeyError:
+ raise ValueError("No such protocol")
+
+ method_obj = method_func()
+
+ context = _api.SSL_CTX_new(method_obj)
+ if context == _api.NULL:
+ 1/0
+
+ self._context = context
+ self._passphrase_callback = None
+ self._verify_callback = None
+ self._info_callback = None
+ self._tlsext_servername_callback = None
+ self._passphrase_userdata = None
+ self._app_data = None
+
+ # SSL_CTX_set_app_data(self->ctx, self);
+ # SSL_CTX_set_mode(self->ctx, SSL_MODE_ENABLE_PARTIAL_WRITE |
+ # SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER |
+ # SSL_MODE_AUTO_RETRY);
+
+
+ def load_verify_locations(self, cafile, capath=None):
+ """
+ Let SSL know where we can find trusted certificates for the certificate
+ chain
+
+ :param cafile: In which file we can find the certificates
+ :param capath: In which directory we can find the certificates
+ :return: None
+ """
+ if cafile is None:
+ cafile = _api.NULL
+ elif not isinstance(cafile, bytes):
+ raise TypeError("cafile must be None or a byte string")
+
+ if capath is None:
+ capath = _api.NULL
+ elif not isinstance(capath, bytes):
+ raise TypeError("capath must be None or a byte string")
+
+ load_result = _api.SSL_CTX_load_verify_locations(self._context, cafile, capath)
+ if not load_result:
+ _raise_current_error(Error)
+
+
+ def _wrap_callback(self, callback):
+ @wraps(callback)
+ def wrapped(size, verify, userdata):
+ return callback(size, verify, self._passphrase_userdata)
+ return _PassphraseHelper(
+ FILETYPE_PEM, wrapped, more_args=True, truncate=True)
+
+
+ def set_passwd_cb(self, callback, userdata=None):
+ """
+ Set the passphrase callback
+
+ :param callback: The Python callback to use
+ :param userdata: (optional) A Python object which will be given as
+ argument to the callback
+ :return: None
+ """
+ if not callable(callback):
+ raise TypeError("callback must be callable")
+
+ self._passphrase_helper = self._wrap_callback(callback)
+ self._passphrase_callback = self._passphrase_helper.callback
+ _api.SSL_CTX_set_default_passwd_cb(
+ self._context, self._passphrase_callback)
+ self._passphrase_userdata = userdata
+
+
+ def set_default_verify_paths(self):
+ """
+ Use the platform-specific CA certificate locations
+
+ :return: None
+ """
+
+ def use_certificate_chain_file(self, certfile):
+ """
+ Load a certificate chain from a file
+
+ :param certfile: The name of the certificate chain file
+ :return: None
+ """
+
+ def use_certificate_file(self, certfile, filetype=_unspecified):
+ """
+ Load a certificate from a file
+
+ :param certfile: The name of the certificate file
+ :param filetype: (optional) The encoding of the file, default is PEM
+ :return: None
+ """
+
+ def use_certificate(self, cert):
+ """
+ Load a certificate from a X509 object
+
+ :param cert: The X509 object
+ :return: None
+ """
+
+ def add_extra_chain_cert(self, certobj):
+ """
+ Add certificate to chain
+
+ :param certobj: The X509 certificate object to add to the chain
+ :return: None
+ """
+ if not isinstance(certobj, X509):
+ raise TypeError("certobj must be an X509 instance")
+
+ copy = _api.X509_dup(certobj._x509)
+ add_result = _api.SSL_CTX_add_extra_chain_cert(self._context, certobj._x509)
+ if not add_result:
+ # _api.X509_free(copy)
+ # _raise_current_error(Error)
+ 1/0
+
+ def use_privatekey_file(self, keyfile, filetype=_unspecified):
+ """
+ Load a private key from a file
+
+ :param keyfile: The name of the key file
+ :param filetype: (optional) The encoding of the file, default is PEM
+ :return: None
+ """
+ if not isinstance(keyfile, bytes):
+ raise TypeError("keyfile must be a byte string")
+
+ if filetype is _unspecified:
+ filetype = FILETYPE_PEM
+ elif not isinstance(filetype, int):
+ raise TypeError("filetype must be an integer")
+
+ use_result = _api.SSL_CTX_use_PrivateKey_file(
+ self._context, keyfile, filetype)
+ if not use_result:
+ exception = self._passphrase_helper.raise_if_problem(Error)
+ if exception is not None:
+ raise exception
+
+
+ def use_privatekey(self, pkey):
+ """
+ Load a private key from a PKey object
+
+ :param pkey: The PKey object
+ :return: None
+ """
+ if not isinstance(pkey, PKey):
+ raise TypeError("pkey must be a PKey instance")
+
+ use_result = _api.SSL_CTX_use_PrivateKey(self._context, pkey._pkey)
+ if not use_result:
+ exception = self._passphrase_helper.raise_if_problem(Error)
+ if exception is not None:
+ raise exception
+
+
+ def check_privatekey(self):
+ """
+ Check that the private key and certificate match up
+
+ :return: None (raises an exception if something's wrong)
+ """
+
+ def load_client_ca(self, cafile):
+ """
+ Load the trusted certificates that will be sent to the client (basically
+ telling the client "These are the guys I trust"). Does not actually
+ imply any of the certificates are trusted; that must be configured
+ separately.
+
+ :param cafile: The name of the certificates file
+ :return: None
+ """
+
+ def set_session_id(self, buf):
+ """
+ Set the session identifier. This is needed if you want to do session
+ resumption.
+
+ :param buf: A Python object that can be safely converted to a string
+ :returns: None
+ """
+
+ def set_session_cache_mode(self, mode):
+ """
+ Enable/disable session caching and specify the mode used.
+
+ :param mode: One or more of the SESS_CACHE_* flags (combine using
+ bitwise or)
+ :returns: The previously set caching mode.
+ """
+ if not isinstance(mode, int):
+ raise TypeError("mode must be an integer")
+
+ return _api.SSL_CTX_set_session_cache_mode(self._context, mode)
+
+
+ def get_session_cache_mode(self):
+ """
+ :returns: The currently used cache mode.
+ """
+ return _api.SSL_CTX_get_session_cache_mode(self._context)
+
+
+ def set_verify(self, mode, callback):
+ """
+ Set the verify mode and verify callback
+
+ :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, int):
+ raise TypeError("mode must be an integer")
+
+ if not callable(callback):
+ raise TypeError("callback must be callable")
+
+ callback = _api.ffi.callback("int(*)(int, X509_STORE_CTX*)", callback)
+ _api.SSL_CTX_set_verify(self._context, mode, callback)
+
+
+ def set_verify_depth(self, depth):
+ """
+ Set the verify depth
+
+ :param depth: An integer specifying the verify depth
+ :return: None
+ """
+ if not isinstance(depth, int):
+ raise TypeError("depth must be an integer")
+
+ _api.SSL_CTX_set_verify_depth(self._context, depth)
+
+
+ def get_verify_mode(self):
+ """
+ Get the verify mode
+
+ :return: The verify mode
+ """
+ return _api.SSL_CTX_get_verify_mode(self._context)
+
+
+ def get_verify_depth(self):
+ """
+ Get the verify depth
+
+ :return: The verify depth
+ """
+ return _api.SSL_CTX_get_verify_depth(self._context)
+
+
+ def load_tmp_dh(self, dhfile):
+ """
+ Load parameters for Ephemeral Diffie-Hellman
+
+ :param dhfile: The file to load EDH parameters from
+ :return: None
+ """
+ if not isinstance(dhfile, bytes):
+ raise TypeError("dhfile must be a byte string")
+
+ bio = _api.BIO_new_file(dhfile, "r")
+ if bio == _api.NULL:
+ _raise_current_error(Error)
+ bio = _api.ffi.gc(bio, _api.BIO_free)
+
+ dh = _api.PEM_read_bio_DHparams(bio, _api.NULL, _api.NULL, _api.NULL)
+ dh = _api.ffi.gc(dh, _api.DH_free)
+ _api.SSL_CTX_set_tmp_dh(self._context, dh)
+
+
+ def set_cipher_list(self, cipher_list):
+ """
+ Change the cipher list
+
+ :param cipher_list: A cipher list, see ciphers(1)
+ :return: None
+ """
+
+ def set_client_ca_list(self, certificate_authorities):
+ """
+ Set the list of preferred client certificate signers for this server context.
+
+ This list of certificate authorities will be sent to the client when the
+ server requests a client certificate.
+
+ :param certificate_authorities: a sequence of X509Names.
+ :return: None
+ """
+
+ def add_client_ca(self, certificate_authority):
+ """
+ Add the CA certificate to the list of preferred signers for this context.
+
+ The list of certificate authorities will be sent to the client when the
+ server requests a client certificate.
+
+ :param certificate_authority: certificate authority's X509 certificate.
+ :return: None
+ """
+
+ def set_timeout(self, timeout):
+ """
+ Set session timeout
+
+ :param timeout: The timeout in seconds
+ :return: The previous session timeout
+ """
+ if not isinstance(timeout, int):
+ raise TypeError("timeout must be an integer")
+
+ return _api.SSL_CTX_set_timeout(self._context, timeout)
+
+
+ def get_timeout(self):
+ """
+ Get the session timeout
+
+ :return: The session timeout
+ """
+ return _api.SSL_CTX_get_timeout(self._context)
+
+
+ def set_info_callback(self, callback):
+ """
+ Set the info callback
+
+ :param callback: The Python callback to use
+ :return: None
+ """
+
+ def get_app_data(self):
+ """
+ Get the application data (supplied via set_app_data())
+
+ :return: The application data
+ """
+ return self._app_data
+
+
+ def set_app_data(self, data):
+ """
+ Set the application data (will be returned from get_app_data())
+
+ :param data: Any Python object
+ :return: None
+ """
+ self._app_data = data
+
+
+ def get_cert_store(self):
+ """
+ Get the certificate store for the context
+
+ :return: A X509Store object
+ """
+
+ def set_options(self, options):
+ """
+ Add options. Options set before are not cleared!
+
+ :param options: The options to add.
+ :return: The new option bitmask.
+ """
+ if not isinstance(options, options):
+ raise TypeError("options must be an integer")
+
+ return _api.SSL_CTX_set_options(self._context, options)
+
+
+ def set_mode(self, mode):
+ """
+ Add modes via bitmask. Modes set before are not cleared!
+
+ :param mode: The mode to add.
+ :return: The new mode bitmask.
+ """
+ if not isinstance(mode, int):
+ raise TypeError("mode must be an integer")
+
+ return _api.SSL_CTX_set_mode(self._context, mode)
+
+
+ def set_tlsext_servername_callback(self, callback):
+ """
+ Specify a callback function to be called when clients specify a server name.
+
+ :param callback: The callback function. It will be invoked with one
+ argument, the Connection instance.
+ """
+
+ContextType = Context
diff --git a/OpenSSL/crypto.py b/OpenSSL/crypto.py
index bb4c0ea..7e0d2b6 100644
--- a/OpenSSL/crypto.py
+++ b/OpenSSL/crypto.py
@@ -81,7 +81,12 @@
-def _raise_current_error():
+class Error(Exception):
+ pass
+
+
+
+def _raise_current_error(exceptionType=Error):
errors = []
while True:
error = _api.ERR_get_error()
@@ -92,14 +97,11 @@
_api.string(_api.ERR_func_error_string(error)),
_api.string(_api.ERR_reason_error_string(error))))
- raise Error(errors)
+ raise exceptionType(errors)
_exception_from_error_queue = _raise_current_error
-class Error(Exception):
- pass
-
class PKey(object):
_only_public = False
@@ -1718,10 +1720,12 @@
class _PassphraseHelper(object):
- def __init__(self, type, passphrase):
+ def __init__(self, type, passphrase, more_args=False, truncate=False):
if type != FILETYPE_PEM and passphrase is not None:
raise ValueError("only FILETYPE_PEM key format supports encryption")
self._passphrase = passphrase
+ self._more_args = more_args
+ self._truncate = truncate
self._problems = []
@@ -1749,22 +1753,29 @@
raise TypeError("Last argument must be string or callable")
- def raise_if_problem(self):
+ def raise_if_problem(self, exceptionType=Error):
+ try:
+ _raise_current_error(exceptionType)
+ except exceptionType as e:
+ pass
if self._problems:
- try:
- _raise_current_error()
- except Error:
- pass
raise self._problems[0]
+ return e
def _read_passphrase(self, buf, size, rwflag, userdata):
try:
- result = self._passphrase(rwflag)
+ if self._more_args:
+ result = self._passphrase(size, rwflag, userdata)
+ else:
+ result = self._passphrase(rwflag)
if not isinstance(result, bytes):
raise ValueError("String expected")
if len(result) > size:
- raise ValueError("passphrase returned by callback is too long")
+ if self._truncate:
+ result = result[:size]
+ else:
+ raise ValueError("passphrase returned by callback is too long")
for i in range(len(result)):
buf[i] = result[i]
return len(result)
diff --git a/OpenSSL/test/test_ssl.py b/OpenSSL/test/test_ssl.py
index 3e4e3da..c5a0222 100644
--- a/OpenSSL/test/test_ssl.py
+++ b/OpenSSL/test/test_ssl.py
@@ -534,7 +534,7 @@
"""
pemFile = self._write_encrypted_pem(b("monkeys are nice"))
def passphraseCallback(maxlen, verify, extra):
- return None
+ return ""
context = Context(TLSv1_METHOD)
context.set_passwd_cb(passphraseCallback)
@@ -552,7 +552,7 @@
context = Context(TLSv1_METHOD)
context.set_passwd_cb(passphraseCallback)
- self.assertRaises(Error, context.use_privatekey_file, pemFile)
+ self.assertRaises(ValueError, context.use_privatekey_file, pemFile)
def test_passwd_callback_too_long(self):