diff options
Diffstat (limited to 'src/OpenSSL/SSL.py')
-rw-r--r-- | src/OpenSSL/SSL.py | 755 |
1 files changed, 340 insertions, 415 deletions
diff --git a/src/OpenSSL/SSL.py b/src/OpenSSL/SSL.py index 0687fc3..e5fea1e 100644 --- a/src/OpenSSL/SSL.py +++ b/src/OpenSSL/SSL.py @@ -6,16 +6,13 @@ from itertools import count, chain from weakref import WeakValueDictionary from errno import errorcode -from cryptography.utils import deprecated - -from six import ( - binary_type as _binary_type, integer_types as integer_types, int2byte, - indexbytes) +from six import integer_types, int2byte, indexbytes from OpenSSL._util import ( UNSPECIFIED as _UNSPECIFIED, exception_from_error_queue as _exception_from_error_queue, ffi as _ffi, + from_buffer as _from_buffer, lib as _lib, make_assert as _make_assert, native as _native, @@ -25,99 +22,108 @@ from OpenSSL._util import ( ) from OpenSSL.crypto import ( - FILETYPE_PEM, _PassphraseHelper, PKey, X509Name, X509, X509Store) + FILETYPE_PEM, + _PassphraseHelper, + PKey, + X509Name, + X509, + X509Store, +) __all__ = [ - 'OPENSSL_VERSION_NUMBER', - 'SSLEAY_VERSION', - 'SSLEAY_CFLAGS', - 'SSLEAY_PLATFORM', - 'SSLEAY_DIR', - 'SSLEAY_BUILT_ON', - 'SENT_SHUTDOWN', - 'RECEIVED_SHUTDOWN', - 'SSLv2_METHOD', - 'SSLv3_METHOD', - 'SSLv23_METHOD', - 'TLSv1_METHOD', - 'TLSv1_1_METHOD', - 'TLSv1_2_METHOD', - 'OP_NO_SSLv2', - 'OP_NO_SSLv3', - 'OP_NO_TLSv1', - 'OP_NO_TLSv1_1', - 'OP_NO_TLSv1_2', - 'MODE_RELEASE_BUFFERS', - 'OP_SINGLE_DH_USE', - 'OP_SINGLE_ECDH_USE', - 'OP_EPHEMERAL_RSA', - 'OP_MICROSOFT_SESS_ID_BUG', - 'OP_NETSCAPE_CHALLENGE_BUG', - 'OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG', - 'OP_SSLREF2_REUSE_CERT_TYPE_BUG', - 'OP_MICROSOFT_BIG_SSLV3_BUFFER', - 'OP_MSIE_SSLV2_RSA_PADDING', - 'OP_SSLEAY_080_CLIENT_DH_BUG', - 'OP_TLS_D5_BUG', - 'OP_TLS_BLOCK_PADDING_BUG', - 'OP_DONT_INSERT_EMPTY_FRAGMENTS', - 'OP_CIPHER_SERVER_PREFERENCE', - 'OP_TLS_ROLLBACK_BUG', - 'OP_PKCS1_CHECK_1', - 'OP_PKCS1_CHECK_2', - 'OP_NETSCAPE_CA_DN_BUG', - 'OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG', - 'OP_NO_COMPRESSION', - 'OP_NO_QUERY_MTU', - 'OP_COOKIE_EXCHANGE', - 'OP_NO_TICKET', - 'OP_ALL', - 'VERIFY_PEER', - 'VERIFY_FAIL_IF_NO_PEER_CERT', - 'VERIFY_CLIENT_ONCE', - 'VERIFY_NONE', - 'SESS_CACHE_OFF', - 'SESS_CACHE_CLIENT', - 'SESS_CACHE_SERVER', - 'SESS_CACHE_BOTH', - 'SESS_CACHE_NO_AUTO_CLEAR', - 'SESS_CACHE_NO_INTERNAL_LOOKUP', - 'SESS_CACHE_NO_INTERNAL_STORE', - 'SESS_CACHE_NO_INTERNAL', - 'SSL_ST_CONNECT', - 'SSL_ST_ACCEPT', - 'SSL_ST_MASK', - 'SSL_CB_LOOP', - 'SSL_CB_EXIT', - 'SSL_CB_READ', - 'SSL_CB_WRITE', - 'SSL_CB_ALERT', - 'SSL_CB_READ_ALERT', - 'SSL_CB_WRITE_ALERT', - 'SSL_CB_ACCEPT_LOOP', - 'SSL_CB_ACCEPT_EXIT', - 'SSL_CB_CONNECT_LOOP', - 'SSL_CB_CONNECT_EXIT', - 'SSL_CB_HANDSHAKE_START', - 'SSL_CB_HANDSHAKE_DONE', - 'Error', - 'WantReadError', - 'WantWriteError', - 'WantX509LookupError', - 'ZeroReturnError', - 'SysCallError', - 'SSLeay_version', - 'Session', - 'Context', - 'Connection' + "OPENSSL_VERSION_NUMBER", + "SSLEAY_VERSION", + "SSLEAY_CFLAGS", + "SSLEAY_PLATFORM", + "SSLEAY_DIR", + "SSLEAY_BUILT_ON", + "SENT_SHUTDOWN", + "RECEIVED_SHUTDOWN", + "SSLv2_METHOD", + "SSLv3_METHOD", + "SSLv23_METHOD", + "TLSv1_METHOD", + "TLSv1_1_METHOD", + "TLSv1_2_METHOD", + "OP_NO_SSLv2", + "OP_NO_SSLv3", + "OP_NO_TLSv1", + "OP_NO_TLSv1_1", + "OP_NO_TLSv1_2", + "OP_NO_TLSv1_3", + "MODE_RELEASE_BUFFERS", + "OP_SINGLE_DH_USE", + "OP_SINGLE_ECDH_USE", + "OP_EPHEMERAL_RSA", + "OP_MICROSOFT_SESS_ID_BUG", + "OP_NETSCAPE_CHALLENGE_BUG", + "OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG", + "OP_SSLREF2_REUSE_CERT_TYPE_BUG", + "OP_MICROSOFT_BIG_SSLV3_BUFFER", + "OP_MSIE_SSLV2_RSA_PADDING", + "OP_SSLEAY_080_CLIENT_DH_BUG", + "OP_TLS_D5_BUG", + "OP_TLS_BLOCK_PADDING_BUG", + "OP_DONT_INSERT_EMPTY_FRAGMENTS", + "OP_CIPHER_SERVER_PREFERENCE", + "OP_TLS_ROLLBACK_BUG", + "OP_PKCS1_CHECK_1", + "OP_PKCS1_CHECK_2", + "OP_NETSCAPE_CA_DN_BUG", + "OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG", + "OP_NO_COMPRESSION", + "OP_NO_QUERY_MTU", + "OP_COOKIE_EXCHANGE", + "OP_NO_TICKET", + "OP_ALL", + "VERIFY_PEER", + "VERIFY_FAIL_IF_NO_PEER_CERT", + "VERIFY_CLIENT_ONCE", + "VERIFY_NONE", + "SESS_CACHE_OFF", + "SESS_CACHE_CLIENT", + "SESS_CACHE_SERVER", + "SESS_CACHE_BOTH", + "SESS_CACHE_NO_AUTO_CLEAR", + "SESS_CACHE_NO_INTERNAL_LOOKUP", + "SESS_CACHE_NO_INTERNAL_STORE", + "SESS_CACHE_NO_INTERNAL", + "SSL_ST_CONNECT", + "SSL_ST_ACCEPT", + "SSL_ST_MASK", + "SSL_CB_LOOP", + "SSL_CB_EXIT", + "SSL_CB_READ", + "SSL_CB_WRITE", + "SSL_CB_ALERT", + "SSL_CB_READ_ALERT", + "SSL_CB_WRITE_ALERT", + "SSL_CB_ACCEPT_LOOP", + "SSL_CB_ACCEPT_EXIT", + "SSL_CB_CONNECT_LOOP", + "SSL_CB_CONNECT_EXIT", + "SSL_CB_HANDSHAKE_START", + "SSL_CB_HANDSHAKE_DONE", + "Error", + "WantReadError", + "WantWriteError", + "WantX509LookupError", + "ZeroReturnError", + "SysCallError", + "SSLeay_version", + "Session", + "Context", + "Connection", ] try: _buffer = buffer except NameError: + class _buffer(object): pass + OPENSSL_VERSION_NUMBER = _lib.OPENSSL_VERSION_NUMBER SSLEAY_VERSION = _lib.SSLEAY_VERSION SSLEAY_CFLAGS = _lib.SSLEAY_CFLAGS @@ -140,6 +146,10 @@ OP_NO_SSLv3 = _lib.SSL_OP_NO_SSLv3 OP_NO_TLSv1 = _lib.SSL_OP_NO_TLSv1 OP_NO_TLSv1_1 = _lib.SSL_OP_NO_TLSv1_1 OP_NO_TLSv1_2 = _lib.SSL_OP_NO_TLSv1_2 +try: + OP_NO_TLSv1_3 = _lib.SSL_OP_NO_TLSv1_3 +except AttributeError: + pass MODE_RELEASE_BUFFERS = _lib.SSL_MODE_RELEASE_BUFFERS @@ -191,17 +201,6 @@ SESS_CACHE_NO_INTERNAL = _lib.SSL_SESS_CACHE_NO_INTERNAL SSL_ST_CONNECT = _lib.SSL_ST_CONNECT SSL_ST_ACCEPT = _lib.SSL_ST_ACCEPT SSL_ST_MASK = _lib.SSL_ST_MASK -if _lib.Cryptography_HAS_SSL_ST: - SSL_ST_INIT = _lib.SSL_ST_INIT - SSL_ST_BEFORE = _lib.SSL_ST_BEFORE - SSL_ST_OK = _lib.SSL_ST_OK - SSL_ST_RENEGOTIATE = _lib.SSL_ST_RENEGOTIATE - __all__.extend([ - 'SSL_ST_INIT', - 'SSL_ST_BEFORE', - 'SSL_ST_OK', - 'SSL_ST_RENEGOTIATE', - ]) SSL_CB_LOOP = _lib.SSL_CB_LOOP SSL_CB_EXIT = _lib.SSL_CB_EXIT @@ -330,97 +329,11 @@ class _VerifyHelper(_CallbackExceptionHelper): 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. - """ - - def __init__(self, callback): - _CallbackExceptionHelper.__init__(self) - - @wraps(callback) - def wrapper(ssl, out, outlen, arg): - try: - conn = Connection._reverse_mapping[ssl] - protos = callback(conn) - - # Join the protocols into a Python bytestring, length-prefixing - # each element. - protostr = b''.join( - chain.from_iterable((int2byte(len(p)), p) for p in protos) - ) - - # Save our callback arguments on the connection object. This is - # done to make sure that they don't get freed before OpenSSL - # uses them. Then, return them appropriately in the output - # parameters. - conn._npn_advertise_callback_args = [ - _ffi.new("unsigned int *", len(protostr)), - _ffi.new("unsigned char[]", protostr), - ] - outlen[0] = conn._npn_advertise_callback_args[0][0] - out[0] = conn._npn_advertise_callback_args[1] - return 0 - except Exception as e: - self._problems.append(e) - return 2 # SSL_TLSEXT_ERR_ALERT_FATAL - - self.callback = _ffi.callback( - "int (*)(SSL *, const unsigned char **, unsigned int *, void *)", - wrapper + "int (*)(int, X509_STORE_CTX *)", wrapper ) -class _NpnSelectHelper(_CallbackExceptionHelper): - """ - Wrap a callback such that it can be used as an NPN selection callback. - """ - - def __init__(self, callback): - _CallbackExceptionHelper.__init__(self) - - @wraps(callback) - def wrapper(ssl, out, outlen, in_, inlen, arg): - try: - conn = Connection._reverse_mapping[ssl] - - # The string passed to us is actually made up of multiple - # length-prefixed bytestrings. We need to split that into a - # list. - instr = _ffi.buffer(in_, inlen)[:] - protolist = [] - while instr: - length = indexbytes(instr, 0) - proto = instr[1:length + 1] - protolist.append(proto) - instr = instr[length + 1:] - - # Call the callback - outstr = callback(conn, protolist) - - # Save our callback arguments on the connection object. This is - # done to make sure that they don't get freed before OpenSSL - # uses them. Then, return them appropriately in the output - # parameters. - conn._npn_select_callback_args = [ - _ffi.new("unsigned char *", len(outstr)), - _ffi.new("unsigned char[]", outstr), - ] - outlen[0] = conn._npn_select_callback_args[0][0] - out[0] = conn._npn_select_callback_args[1] - return 0 - except Exception as e: - self._problems.append(e) - return 2 # SSL_TLSEXT_ERR_ALERT_FATAL - - self.callback = _ffi.callback( - ("int (*)(SSL *, unsigned char **, unsigned char *, " - "const unsigned char *, unsigned int, void *)"), - wrapper - ) +NO_OVERLAPPING_PROTOCOLS = object() class _ALPNSelectHelper(_CallbackExceptionHelper): @@ -443,34 +356,44 @@ class _ALPNSelectHelper(_CallbackExceptionHelper): protolist = [] while instr: encoded_len = indexbytes(instr, 0) - proto = instr[1:encoded_len + 1] + proto = instr[1 : encoded_len + 1] protolist.append(proto) - instr = instr[encoded_len + 1:] + instr = instr[encoded_len + 1 :] # Call the callback - outstr = callback(conn, protolist) - - if not isinstance(outstr, _binary_type): - raise TypeError("ALPN callback must return a bytestring.") + outbytes = callback(conn, protolist) + any_accepted = True + if outbytes is NO_OVERLAPPING_PROTOCOLS: + outbytes = b"" + any_accepted = False + elif not isinstance(outbytes, bytes): + raise TypeError( + "ALPN callback must return a bytestring or the " + "special NO_OVERLAPPING_PROTOCOLS sentinel value." + ) # Save our callback arguments on the connection object to make # sure that they don't get freed before OpenSSL can use them. # Then, return them in the appropriate output parameters. conn._alpn_select_callback_args = [ - _ffi.new("unsigned char *", len(outstr)), - _ffi.new("unsigned char[]", outstr), + _ffi.new("unsigned char *", len(outbytes)), + _ffi.new("unsigned char[]", outbytes), ] outlen[0] = conn._alpn_select_callback_args[0][0] out[0] = conn._alpn_select_callback_args[1] - return 0 + if not any_accepted: + return _lib.SSL_TLSEXT_ERR_NOACK + return _lib.SSL_TLSEXT_ERR_OK except Exception as e: self._problems.append(e) - return 2 # SSL_TLSEXT_ERR_ALERT_FATAL + return _lib.SSL_TLSEXT_ERR_ALERT_FATAL self.callback = _ffi.callback( - ("int (*)(SSL *, unsigned char **, unsigned char *, " - "const unsigned char *, unsigned int, void *)"), - wrapper + ( + "int (*)(SSL *, unsigned char **, unsigned char *, " + "const unsigned char *, unsigned int, void *)" + ), + wrapper, ) @@ -513,7 +436,7 @@ class _OCSPServerCallbackHelper(_CallbackExceptionHelper): # Call the callback. ocsp_data = callback(conn, data) - if not isinstance(ocsp_data, _binary_type): + if not isinstance(ocsp_data, bytes): raise TypeError("OCSP callback must return a bytestring.") # If the OCSP data was provided, we will pass it to OpenSSL. @@ -582,7 +505,7 @@ class _OCSPClientCallbackHelper(_CallbackExceptionHelper): ocsp_len = _lib.SSL_get_tlsext_status_ocsp_resp(ssl, ocsp_ptr) if ocsp_len < 0: # No OCSP data. - ocsp_data = b'' + ocsp_data = b"" else: # Copy the OCSP data, then pass it to the callback. ocsp_data = _ffi.buffer(ocsp_ptr[0], ocsp_len)[:] @@ -614,7 +537,8 @@ def _asFileDescriptor(obj): raise TypeError("argument must be an int, or have a fileno() method.") elif fd < 0: raise ValueError( - "file descriptor cannot be a negative integer (%i)" % (fd,)) + "file descriptor cannot be a negative integer (%i)" % (fd,) + ) return fd @@ -638,11 +562,14 @@ def _make_requires(flag, error): ``Cryptography_HAS_NEXTPROTONEG``. :param error: The string to be used in the exception if the flag is false. """ + def _requires_decorator(func): if not flag: + @wraps(func) def explode(*args, **kwargs): raise NotImplementedError(error) + return explode else: return func @@ -650,18 +577,13 @@ def _make_requires(flag, error): return _requires_decorator -_requires_npn = _make_requires( - _lib.Cryptography_HAS_NEXTPROTONEG, "NPN not available" -) - - _requires_alpn = _make_requires( _lib.Cryptography_HAS_ALPN, "ALPN not available" ) -_requires_sni = _make_requires( - _lib.Cryptography_HAS_TLSEXT_HOSTNAME, "SNI not available" +_requires_keylog = _make_requires( + getattr(_lib, "Cryptography_HAS_KEYLOG", None), "Key logging not available" ) @@ -673,6 +595,7 @@ class Session(object): .. versionadded:: 0.14 """ + pass @@ -684,6 +607,7 @@ class Context(object): :param method: One of SSLv2_METHOD, SSLv3_METHOD, SSLv23_METHOD, or TLSv1_METHOD. """ + _methods = { SSLv2_METHOD: "SSLv2_method", SSLv3_METHOD: "SSLv3_method", @@ -695,7 +619,8 @@ class Context(object): _methods = dict( (identifier, getattr(_lib, name)) for (identifier, name) in _methods.items() - if getattr(_lib, name, None) is not None) + if getattr(_lib, name, None) is not None + ) def __init__(self, method): if not isinstance(method, integer_types): @@ -713,14 +638,11 @@ class Context(object): _openssl_assert(context != _ffi.NULL) context = _ffi.gc(context, _lib.SSL_CTX_free) - # If SSL_CTX_set_ecdh_auto is available then set it so the ECDH curve - # will be auto-selected. This function was added in 1.0.2 and made a - # noop in 1.1.0+ (where it is set automatically). - try: - res = _lib.SSL_CTX_set_ecdh_auto(context, 1) - _openssl_assert(res == 1) - except AttributeError: - pass + # Set SSL_CTX_set_ecdh_auto so that the ECDH curve will be + # auto-selected. This function was added in 1.0.2 and made a noop in + # 1.1.0+ (where it is set automatically). + res = _lib.SSL_CTX_set_ecdh_auto(context, 1) + _openssl_assert(res == 1) self._context = context self._passphrase_helper = None @@ -729,12 +651,9 @@ class Context(object): self._verify_helper = None self._verify_callback = None self._info_callback = None + self._keylog_callback = None self._tlsext_servername_callback = None self._app_data = None - self._npn_advertise_helper = None - self._npn_advertise_callback = None - self._npn_select_helper = None - self._npn_select_callback = None self._alpn_select_helper = None self._alpn_select_callback = None self._ocsp_helper = None @@ -779,8 +698,10 @@ class Context(object): @wraps(callback) def wrapper(size, verify, userdata): return callback(size, verify, self._passphrase_userdata) + return _PassphraseHelper( - FILETYPE_PEM, wrapper, more_args=True, truncate=True) + FILETYPE_PEM, wrapper, more_args=True, truncate=True + ) def set_passwd_cb(self, callback, userdata=None): """ @@ -807,7 +728,8 @@ class Context(object): self._passphrase_helper = self._wrap_callback(callback) self._passphrase_callback = self._passphrase_helper.callback _lib.SSL_CTX_set_default_passwd_cb( - self._context, self._passphrase_callback) + self._context, self._passphrase_callback + ) self._passphrase_userdata = userdata def set_default_verify_paths(self): @@ -837,9 +759,9 @@ class Context(object): # First we'll check to see if any env vars have been set. If so, # we won't try to do anything else because the user has set the path # themselves. - dir_env_var = _ffi.string( - _lib.X509_get_default_cert_dir_env() - ).decode("ascii") + dir_env_var = _ffi.string(_lib.X509_get_default_cert_dir_env()).decode( + "ascii" + ) file_env_var = _ffi.string( _lib.X509_get_default_cert_file_env() ).decode("ascii") @@ -850,13 +772,12 @@ class Context(object): # to the exact values we use in our manylinux1 builds. If they are # then we know to load the fallbacks if ( - default_dir == _CRYPTOGRAPHY_MANYLINUX1_CA_DIR and - default_file == _CRYPTOGRAPHY_MANYLINUX1_CA_FILE + default_dir == _CRYPTOGRAPHY_MANYLINUX1_CA_DIR + and default_file == _CRYPTOGRAPHY_MANYLINUX1_CA_FILE ): # This is manylinux1, let's load our fallback paths self._fallback_default_verify_paths( - _CERTIFICATE_FILE_LOCATIONS, - _CERTIFICATE_PATH_LOCATIONS + _CERTIFICATE_FILE_LOCATIONS, _CERTIFICATE_PATH_LOCATIONS ) def _check_env_vars_set(self, dir_env_var, file_env_var): @@ -866,8 +787,8 @@ class Context(object): :return: bool """ return ( - os.environ.get(file_env_var) is not None or - os.environ.get(dir_env_var) is not None + os.environ.get(file_env_var) is not None + or os.environ.get(dir_env_var) is not None ) def _fallback_default_verify_paths(self, file_path, dir_path): @@ -985,7 +906,8 @@ class Context(object): raise TypeError("filetype must be an integer") use_result = _lib.SSL_CTX_use_PrivateKey_file( - self._context, keyfile, filetype) + self._context, keyfile, filetype + ) if not use_result: self._raise_passphrase_exception() @@ -1041,11 +963,8 @@ class Context(object): """ buf = _text_to_bytes_and_warn("buf", buf) _openssl_assert( - _lib.SSL_CTX_set_session_id_context( - self._context, - buf, - len(buf), - ) == 1 + _lib.SSL_CTX_set_session_id_context(self._context, buf, len(buf)) + == 1 ) def set_session_cache_mode(self, mode): @@ -1075,21 +994,22 @@ class Context(object): """ return _lib.SSL_CTX_get_session_cache_mode(self._context) - def set_verify(self, mode, callback): + def set_verify(self, mode, callback=None): """ - et the verification flags for this Context object to *mode* and specify - that *callback* should be used for verification callbacks. + Set the verification flags for this Context object to *mode* and + specify that *callback* should be used for verification callbacks. :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. + :param callback: The optional Python verification 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. + If omitted, OpenSSL's default verification is used. :return: None See SSL_CTX_set_verify(3SSL) for further details. @@ -1097,12 +1017,17 @@ class Context(object): if not isinstance(mode, integer_types): raise TypeError("mode must be an integer") - if not callable(callback): - raise TypeError("callback must be callable") + if callback is None: + self._verify_helper = None + self._verify_callback = None + _lib.SSL_CTX_set_verify(self._context, mode, _ffi.NULL) + else: + if not callable(callback): + raise TypeError("callback must be callable") - self._verify_helper = _VerifyHelper(callback) - self._verify_callback = self._verify_helper.callback - _lib.SSL_CTX_set_verify(self._context, mode, self._verify_callback) + self._verify_helper = _VerifyHelper(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): """ @@ -1153,7 +1078,8 @@ class Context(object): dh = _lib.PEM_read_bio_DHparams(bio, _ffi.NULL, _ffi.NULL, _ffi.NULL) dh = _ffi.gc(dh, _lib.DH_free) - _lib.SSL_CTX_set_tmp_dh(self._context, dh) + res = _lib.SSL_CTX_set_tmp_dh(self._context, dh) + _openssl_assert(res == 1) def set_tmp_ecdh(self, curve): """ @@ -1191,13 +1117,20 @@ class Context(object): # invalid cipher string is passed, but without the following check # for the TLS 1.3 specific cipher suites it would never error. tmpconn = Connection(self, None) - _openssl_assert( - tmpconn.get_cipher_list() != [ - 'TLS_AES_256_GCM_SHA384', - 'TLS_CHACHA20_POLY1305_SHA256', - 'TLS_AES_128_GCM_SHA256' - ] - ) + if tmpconn.get_cipher_list() == [ + "TLS_AES_256_GCM_SHA384", + "TLS_CHACHA20_POLY1305_SHA256", + "TLS_AES_128_GCM_SHA256", + ]: + raise Error( + [ + ( + "SSL routines", + "SSL_CTX_set_cipher_list", + "no cipher match", + ), + ], + ) def set_client_ca_list(self, certificate_authorities): """ @@ -1220,9 +1153,7 @@ class Context(object): if not isinstance(ca_name, X509Name): raise TypeError( "client CAs must be X509Name objects, not %s " - "objects" % ( - type(ca_name).__name__, - ) + "objects" % (type(ca_name).__name__,) ) copy = _lib.X509_NAME_dup(ca_name._name) _openssl_assert(copy != _ffi.NULL) @@ -1253,7 +1184,8 @@ class Context(object): raise TypeError("certificate_authority must be an X509 instance") add_result = _lib.SSL_CTX_add_client_CA( - self._context, certificate_authority._x509) + self._context, certificate_authority._x509 + ) _openssl_assert(add_result == 1) def set_timeout(self, timeout): @@ -1291,13 +1223,41 @@ class Context(object): function call. :return: None """ + @wraps(callback) def wrapper(ssl, where, return_code): callback(Connection._reverse_mapping[ssl], where, return_code) + self._info_callback = _ffi.callback( - "void (*)(const SSL *, int, int)", wrapper) + "void (*)(const SSL *, int, int)", wrapper + ) _lib.SSL_CTX_set_info_callback(self._context, self._info_callback) + @_requires_keylog + def set_keylog_callback(self, callback): + """ + Set the TLS key logging callback to *callback*. This function will be + called whenever TLS key material is generated or received, in order + to allow applications to store this keying material for debugging + purposes. + + :param callback: The Python callback to use. This should take two + arguments: a Connection object and a bytestring that contains + the key material in the format used by NSS for its SSLKEYLOGFILE + debugging output. + :return: None + """ + + @wraps(callback) + def wrapper(ssl, line): + line = _ffi.string(line) + callback(Connection._reverse_mapping[ssl], line) + + self._keylog_callback = _ffi.callback( + "void (*)(const SSL *, const char *)", wrapper + ) + _lib.SSL_CTX_set_keylog_callback(self._context, self._keylog_callback) + def get_app_data(self): """ Get the application data (supplied via :meth:`set_app_data()`) @@ -1358,7 +1318,6 @@ class Context(object): return _lib.SSL_CTX_set_mode(self._context, mode) - @_requires_sni def set_tlsext_servername_callback(self, callback): """ Specify a callback function to be called when clients specify a server @@ -1369,15 +1328,18 @@ class Context(object): .. versionadded:: 0.13 """ + @wraps(callback) def wrapper(ssl, alert, arg): callback(Connection._reverse_mapping[ssl]) return 0 self._tlsext_servername_callback = _ffi.callback( - "int (*)(SSL *, int *, void *)", wrapper) + "int (*)(SSL *, int *, void *)", wrapper + ) _lib.SSL_CTX_set_tlsext_servername_callback( - self._context, self._tlsext_servername_callback) + self._context, self._tlsext_servername_callback + ) def set_tlsext_use_srtp(self, profiles): """ @@ -1394,43 +1356,6 @@ class Context(object): _lib.SSL_CTX_set_tlsext_use_srtp(self._context, profiles) == 0 ) - @_requires_npn - def set_npn_advertise_callback(self, callback): - """ - Specify a callback function that will be called when offering `Next - Protocol Negotiation - <https://technotes.googlecode.com/git/nextprotoneg.html>`_ as a server. - - :param callback: The callback function. It will be invoked with one - argument, the :class:`Connection` instance. It should return a - list of bytestrings representing the advertised protocols, like - ``[b'http/1.1', b'spdy/2']``. - - .. versionadded:: 0.15 - """ - self._npn_advertise_helper = _NpnAdvertiseHelper(callback) - self._npn_advertise_callback = self._npn_advertise_helper.callback - _lib.SSL_CTX_set_next_protos_advertised_cb( - self._context, self._npn_advertise_callback, _ffi.NULL) - - @_requires_npn - def set_npn_select_callback(self, callback): - """ - Specify a callback function that will be called when a server offers - Next Protocol Negotiation options. - - :param callback: The callback function. It will be invoked with two - arguments: the Connection, and a list of offered protocols as - bytestrings, e.g. ``[b'http/1.1', b'spdy/2']``. It should return - one of those bytestrings, the chosen protocol. - - .. versionadded:: 0.15 - """ - self._npn_select_helper = _NpnSelectHelper(callback) - self._npn_select_callback = self._npn_select_helper.callback - _lib.SSL_CTX_set_next_proto_select_cb( - self._context, self._npn_select_callback, _ffi.NULL) - @_requires_alpn def set_alpn_protos(self, protos): """ @@ -1444,7 +1369,7 @@ class Context(object): """ # Take the list of protocols and join them together, prefixing them # with their lengths. - protostr = b''.join( + protostr = b"".join( chain.from_iterable((int2byte(len(p)), p) for p in protos) ) @@ -1461,13 +1386,18 @@ class Context(object): :param callback: The callback function. It will be invoked with two arguments: the Connection, and a list of offered protocols as - bytestrings, e.g ``[b'http/1.1', b'spdy/2']``. It should return - one of those bytestrings, the chosen protocol. + bytestrings, e.g ``[b'http/1.1', b'spdy/2']``. It can return + one of those bytestrings to indicate the chosen protocol, the + empty bytestring to terminate the TLS connection, or the + :py:obj:`NO_OVERLAPPING_PROTOCOLS` to indicate that no offered + protocol was selected, but that the connection should not be + aborted. """ self._alpn_select_helper = _ALPNSelectHelper(callback) self._alpn_select_callback = self._alpn_select_helper.callback _lib.SSL_CTX_set_alpn_select_cb( - self._context, self._alpn_select_callback, _ffi.NULL) + self._context, self._alpn_select_callback, _ffi.NULL + ) def _set_ocsp_callback(self, helper, data): """ @@ -1528,15 +1458,7 @@ class Context(object): self._set_ocsp_callback(helper, data) -ContextType = deprecated( - Context, __name__, - "ContextType has been deprecated, use Context instead", DeprecationWarning -) - - class Connection(object): - """ - """ _reverse_mapping = WeakValueDictionary() def __init__(self, context, socket=None): @@ -1560,19 +1482,18 @@ class Connection(object): self._context = context self._app_data = None - # References to strings used for Next Protocol Negotiation. OpenSSL's - # header files suggest that these might get copied at some point, but - # doesn't specify when, so we store them here to make sure they don't - # get freed before OpenSSL uses them. - self._npn_advertise_callback_args = None - self._npn_select_callback_args = None - # References to strings used for Application Layer Protocol # Negotiation. These strings get copied at some point but it's well # after the callback returns, so we have to hang them somewhere to # avoid them getting freed. self._alpn_select_callback_args = None + # Reference the verify_callback of the Context. This ensures that if + # set_verify is called again after the SSL object has been created we + # do not point to a dangling reference + self._verify_helper = context._verify_helper + self._verify_callback = context._verify_callback + self._reverse_mapping[self._ssl] = self if socket is None: @@ -1590,7 +1511,8 @@ class Connection(object): self._from_ssl = None self._socket = socket set_result = _lib.SSL_set_fd( - self._ssl, _asFileDescriptor(self._socket)) + self._ssl, _asFileDescriptor(self._socket) + ) _openssl_assert(set_result == 1) def __getattr__(self, name): @@ -1599,19 +1521,16 @@ class Connection(object): on the Connection object. """ if self._socket is None: - raise AttributeError("'%s' object has no attribute '%s'" % ( - self.__class__.__name__, name - )) + raise AttributeError( + "'%s' object has no attribute '%s'" + % (self.__class__.__name__, name) + ) else: return getattr(self._socket, name) def _raise_ssl_error(self, ssl, result): if self._context._verify_helper is not None: self._context._verify_helper.raise_if_problem() - if self._context._npn_advertise_helper is not None: - self._context._npn_advertise_helper.raise_if_problem() - if self._context._npn_select_helper is not None: - self._context._npn_select_helper.raise_if_problem() if self._context._alpn_select_helper is not None: self._context._alpn_select_helper.raise_if_problem() if self._context._ocsp_helper is not None: @@ -1666,7 +1585,6 @@ class Connection(object): _lib.SSL_set_SSL_CTX(self._ssl, context._context) self._context = context - @_requires_sni def get_servername(self): """ Retrieve the servername extension value if provided in the client hello @@ -1684,7 +1602,6 @@ class Connection(object): return _ffi.string(name) - @_requires_sni def set_tlsext_host_name(self, name): """ Set the value of the servername extension to send in the client hello. @@ -1724,18 +1641,18 @@ class Connection(object): # Backward compatibility buf = _text_to_bytes_and_warn("buf", buf) - if isinstance(buf, memoryview): - buf = buf.tobytes() - if isinstance(buf, _buffer): - buf = str(buf) - if not isinstance(buf, bytes): - raise TypeError("data must be a memoryview, buffer or byte string") - if len(buf) > 2147483647: - raise ValueError("Cannot send more than 2**31-1 bytes at once.") + with _from_buffer(buf) as data: + # check len(buf) instead of len(data) for testability + if len(buf) > 2147483647: + raise ValueError( + "Cannot send more than 2**31-1 bytes at once." + ) + + result = _lib.SSL_write(self._ssl, data, len(data)) + self._raise_ssl_error(self._ssl, result) + + return result - result = _lib.SSL_write(self._ssl, buf, len(buf)) - self._raise_ssl_error(self._ssl, result) - return result write = send def sendall(self, buf, flags=0): @@ -1751,28 +1668,22 @@ class Connection(object): """ buf = _text_to_bytes_and_warn("buf", buf) - if isinstance(buf, memoryview): - buf = buf.tobytes() - if isinstance(buf, _buffer): - buf = str(buf) - if not isinstance(buf, bytes): - raise TypeError("buf must be a memoryview, buffer or byte string") - - left_to_send = len(buf) - total_sent = 0 - data = _ffi.new("char[]", buf) - - while left_to_send: - # SSL_write's num arg is an int, - # so we cannot send more than 2**31-1 bytes at once. - result = _lib.SSL_write( - self._ssl, - data + total_sent, - min(left_to_send, 2147483647) - ) - self._raise_ssl_error(self._ssl, result) - total_sent += result - left_to_send -= result + with _from_buffer(buf) as data: + + left_to_send = len(buf) + total_sent = 0 + + while left_to_send: + # SSL_write's num arg is an int, + # so we cannot send more than 2**31-1 bytes at once. + result = _lib.SSL_write( + self._ssl, data + total_sent, min(left_to_send, 2147483647) + ) + self._raise_ssl_error(self._ssl, result) + total_sent += result + left_to_send -= result + + return total_sent def recv(self, bufsiz, flags=None): """ @@ -1790,6 +1701,7 @@ class Connection(object): result = _lib.SSL_read(self._ssl, buf, bufsiz) self._raise_ssl_error(self._ssl, result) return _ffi.buffer(buf, result)[:] + read = recv def recv_into(self, buffer, nbytes=None, flags=None): @@ -1886,10 +1798,11 @@ class Connection(object): if self._into_ssl is None: raise TypeError("Connection sock was not None") - result = _lib.BIO_write(self._into_ssl, buf, len(buf)) - if result <= 0: - self._handle_bio_errors(self._into_ssl, result) - return result + with _from_buffer(buf) as data: + result = _lib.BIO_write(self._into_ssl, data, len(data)) + if result <= 0: + self._handle_bio_errors(self._into_ssl, result) + return result def renegotiate(self): """ @@ -1906,7 +1819,7 @@ class Connection(object): def do_handshake(self): """ Perform an SSL handshake (usually called after :meth:`renegotiate` or - one of :meth:`set_accept_state` or :meth:`set_accept_state`). This can + one of :meth:`set_accept_state` or :meth:`set_connect_state`). This can raise the same exceptions as :meth:`send` and :meth:`recv`. :return: None. @@ -2055,7 +1968,8 @@ class Connection(object): :raise: NotImplementedError """ raise NotImplementedError( - "Cannot make file object of OpenSSL.SSL.Connection") + "Cannot make file object of OpenSSL.SSL.Connection" + ) def get_app_data(self): """ @@ -2114,7 +2028,7 @@ class Connection(object): if session == _ffi.NULL: return None length = _lib.SSL_get_server_random(self._ssl, _ffi.NULL, 0) - assert length > 0 + _openssl_assert(length > 0) outp = _no_zero_allocator("unsigned char[]", length) _lib.SSL_get_server_random(self._ssl, outp, length) return _ffi.buffer(outp, length)[:] @@ -2130,7 +2044,7 @@ class Connection(object): return None length = _lib.SSL_get_client_random(self._ssl, _ffi.NULL, 0) - assert length > 0 + _openssl_assert(length > 0) outp = _no_zero_allocator("unsigned char[]", length) _lib.SSL_get_client_random(self._ssl, outp, length) return _ffi.buffer(outp, length)[:] @@ -2146,7 +2060,7 @@ class Connection(object): return None length = _lib.SSL_SESSION_get_master_key(session, _ffi.NULL, 0) - assert length > 0 + _openssl_assert(length > 0) outp = _no_zero_allocator("unsigned char[]", length) _lib.SSL_SESSION_get_master_key(session, outp, length) return _ffi.buffer(outp, length)[:] @@ -2168,10 +2082,16 @@ class Connection(object): context_buf = context context_len = len(context) use_context = 1 - success = _lib.SSL_export_keying_material(self._ssl, outp, olen, - label, len(label), - context_buf, context_len, - use_context) + success = _lib.SSL_export_keying_material( + self._ssl, + outp, + olen, + label, + len(label), + context_buf, + context_len, + use_context, + ) _openssl_assert(success == 1) return _ffi.buffer(outp, olen)[:] @@ -2207,6 +2127,22 @@ class Connection(object): return X509._from_raw_x509_ptr(cert) return None + @staticmethod + def _cert_stack_to_list(cert_stack): + """ + Internal helper to convert a STACK_OF(X509) to a list of X509 + instances. + """ + result = [] + for i in range(_lib.sk_X509_num(cert_stack)): + cert = _lib.sk_X509_value(cert_stack, i) + _openssl_assert(cert != _ffi.NULL) + res = _lib.X509_up_ref(cert) + _openssl_assert(res >= 1) + pycert = X509._from_raw_x509_ptr(cert) + result.append(pycert) + return result + def get_peer_cert_chain(self): """ Retrieve the other side's certificate (if any) @@ -2218,13 +2154,26 @@ class Connection(object): if cert_stack == _ffi.NULL: return None - result = [] - for i in range(_lib.sk_X509_num(cert_stack)): - # TODO could incref instead of dup here - cert = _lib.X509_dup(_lib.sk_X509_value(cert_stack, i)) - pycert = X509._from_raw_x509_ptr(cert) - result.append(pycert) - return result + return self._cert_stack_to_list(cert_stack) + + def get_verified_chain(self): + """ + Retrieve the verified certificate chain of the peer including the + peer's end entity certificate. It must be called after a session has + been successfully established. If peer verification was not successful + the chain may be incomplete, invalid, or None. + + :return: A list of X509 instances giving the peer's verified + certificate chain, or None if it does not have one. + + .. versionadded:: 20.0 + """ + # OpenSSL 1.1+ + cert_stack = _lib.SSL_get0_verified_chain(self._ssl) + if cert_stack == _ffi.NULL: + return None + + return self._cert_stack_to_list(cert_stack) def want_read(self): """ @@ -2292,8 +2241,7 @@ class Connection(object): raise TypeError("session must be a Session instance") result = _lib.SSL_set_session(self._ssl, session._session) - if not result: - _raise_current_error() + _openssl_assert(result == 1) def _get_finished_message(self, function): """ @@ -2426,23 +2374,6 @@ class Connection(object): version = _lib.SSL_version(self._ssl) return version - @_requires_npn - def get_next_proto_negotiated(self): - """ - Get the protocol that was negotiated by NPN. - - :returns: A bytestring of the protocol name. If no protocol has been - negotiated yet, returns an empty string. - - .. versionadded:: 0.15 - """ - data = _ffi.new("unsigned char **") - data_len = _ffi.new("unsigned int *") - - _lib.SSL_get0_next_proto_negotiated(self._ssl, data, data_len) - - return _ffi.buffer(data[0], data_len[0])[:] - @_requires_alpn def set_alpn_protos(self, protos): """ @@ -2456,7 +2387,7 @@ class Connection(object): """ # Take the list of protocols and join them together, prefixing them # with their lengths. - protostr = b''.join( + protostr = b"".join( chain.from_iterable((int2byte(len(p)), p) for p in protos) ) @@ -2479,7 +2410,7 @@ class Connection(object): _lib.SSL_get0_alpn_selected(self._ssl, data, data_len) if not data_len: - return b'' + return b"" return _ffi.buffer(data[0], data_len[0])[:] @@ -2496,12 +2427,6 @@ class Connection(object): _openssl_assert(rc == 1) -ConnectionType = deprecated( - Connection, __name__, - "ConnectionType has been deprecated, use Connection instead", - DeprecationWarning -) - # This is similar to the initialization calls at the end of OpenSSL/crypto.py # but is exercised mostly by the Context initializer. _lib.SSL_library_init() |