2499 lines
87 KiB
Python
2499 lines
87 KiB
Python
import os
|
|
import socket
|
|
from sys import platform
|
|
from functools import wraps, partial
|
|
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 OpenSSL._util import (
|
|
UNSPECIFIED as _UNSPECIFIED,
|
|
exception_from_error_queue as _exception_from_error_queue,
|
|
ffi as _ffi,
|
|
lib as _lib,
|
|
make_assert as _make_assert,
|
|
native as _native,
|
|
path_string as _path_string,
|
|
text_to_bytes_and_warn as _text_to_bytes_and_warn,
|
|
no_zero_allocator as _no_zero_allocator,
|
|
)
|
|
|
|
from OpenSSL.crypto import (
|
|
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'
|
|
]
|
|
|
|
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
|
|
SSLEAY_PLATFORM = _lib.SSLEAY_PLATFORM
|
|
SSLEAY_DIR = _lib.SSLEAY_DIR
|
|
SSLEAY_BUILT_ON = _lib.SSLEAY_BUILT_ON
|
|
|
|
SENT_SHUTDOWN = _lib.SSL_SENT_SHUTDOWN
|
|
RECEIVED_SHUTDOWN = _lib.SSL_RECEIVED_SHUTDOWN
|
|
|
|
SSLv2_METHOD = 1
|
|
SSLv3_METHOD = 2
|
|
SSLv23_METHOD = 3
|
|
TLSv1_METHOD = 4
|
|
TLSv1_1_METHOD = 5
|
|
TLSv1_2_METHOD = 6
|
|
|
|
OP_NO_SSLv2 = _lib.SSL_OP_NO_SSLv2
|
|
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
|
|
|
|
MODE_RELEASE_BUFFERS = _lib.SSL_MODE_RELEASE_BUFFERS
|
|
|
|
OP_SINGLE_DH_USE = _lib.SSL_OP_SINGLE_DH_USE
|
|
OP_SINGLE_ECDH_USE = _lib.SSL_OP_SINGLE_ECDH_USE
|
|
OP_EPHEMERAL_RSA = _lib.SSL_OP_EPHEMERAL_RSA
|
|
OP_MICROSOFT_SESS_ID_BUG = _lib.SSL_OP_MICROSOFT_SESS_ID_BUG
|
|
OP_NETSCAPE_CHALLENGE_BUG = _lib.SSL_OP_NETSCAPE_CHALLENGE_BUG
|
|
OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG = (
|
|
_lib.SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG
|
|
)
|
|
OP_SSLREF2_REUSE_CERT_TYPE_BUG = _lib.SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG
|
|
OP_MICROSOFT_BIG_SSLV3_BUFFER = _lib.SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER
|
|
OP_MSIE_SSLV2_RSA_PADDING = _lib.SSL_OP_MSIE_SSLV2_RSA_PADDING
|
|
OP_SSLEAY_080_CLIENT_DH_BUG = _lib.SSL_OP_SSLEAY_080_CLIENT_DH_BUG
|
|
OP_TLS_D5_BUG = _lib.SSL_OP_TLS_D5_BUG
|
|
OP_TLS_BLOCK_PADDING_BUG = _lib.SSL_OP_TLS_BLOCK_PADDING_BUG
|
|
OP_DONT_INSERT_EMPTY_FRAGMENTS = _lib.SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
|
|
OP_CIPHER_SERVER_PREFERENCE = _lib.SSL_OP_CIPHER_SERVER_PREFERENCE
|
|
OP_TLS_ROLLBACK_BUG = _lib.SSL_OP_TLS_ROLLBACK_BUG
|
|
OP_PKCS1_CHECK_1 = _lib.SSL_OP_PKCS1_CHECK_1
|
|
OP_PKCS1_CHECK_2 = _lib.SSL_OP_PKCS1_CHECK_2
|
|
OP_NETSCAPE_CA_DN_BUG = _lib.SSL_OP_NETSCAPE_CA_DN_BUG
|
|
OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG = (
|
|
_lib.SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG
|
|
)
|
|
OP_NO_COMPRESSION = _lib.SSL_OP_NO_COMPRESSION
|
|
|
|
OP_NO_QUERY_MTU = _lib.SSL_OP_NO_QUERY_MTU
|
|
OP_COOKIE_EXCHANGE = _lib.SSL_OP_COOKIE_EXCHANGE
|
|
OP_NO_TICKET = _lib.SSL_OP_NO_TICKET
|
|
|
|
OP_ALL = _lib.SSL_OP_ALL
|
|
|
|
VERIFY_PEER = _lib.SSL_VERIFY_PEER
|
|
VERIFY_FAIL_IF_NO_PEER_CERT = _lib.SSL_VERIFY_FAIL_IF_NO_PEER_CERT
|
|
VERIFY_CLIENT_ONCE = _lib.SSL_VERIFY_CLIENT_ONCE
|
|
VERIFY_NONE = _lib.SSL_VERIFY_NONE
|
|
|
|
SESS_CACHE_OFF = _lib.SSL_SESS_CACHE_OFF
|
|
SESS_CACHE_CLIENT = _lib.SSL_SESS_CACHE_CLIENT
|
|
SESS_CACHE_SERVER = _lib.SSL_SESS_CACHE_SERVER
|
|
SESS_CACHE_BOTH = _lib.SSL_SESS_CACHE_BOTH
|
|
SESS_CACHE_NO_AUTO_CLEAR = _lib.SSL_SESS_CACHE_NO_AUTO_CLEAR
|
|
SESS_CACHE_NO_INTERNAL_LOOKUP = _lib.SSL_SESS_CACHE_NO_INTERNAL_LOOKUP
|
|
SESS_CACHE_NO_INTERNAL_STORE = _lib.SSL_SESS_CACHE_NO_INTERNAL_STORE
|
|
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
|
|
SSL_CB_READ = _lib.SSL_CB_READ
|
|
SSL_CB_WRITE = _lib.SSL_CB_WRITE
|
|
SSL_CB_ALERT = _lib.SSL_CB_ALERT
|
|
SSL_CB_READ_ALERT = _lib.SSL_CB_READ_ALERT
|
|
SSL_CB_WRITE_ALERT = _lib.SSL_CB_WRITE_ALERT
|
|
SSL_CB_ACCEPT_LOOP = _lib.SSL_CB_ACCEPT_LOOP
|
|
SSL_CB_ACCEPT_EXIT = _lib.SSL_CB_ACCEPT_EXIT
|
|
SSL_CB_CONNECT_LOOP = _lib.SSL_CB_CONNECT_LOOP
|
|
SSL_CB_CONNECT_EXIT = _lib.SSL_CB_CONNECT_EXIT
|
|
SSL_CB_HANDSHAKE_START = _lib.SSL_CB_HANDSHAKE_START
|
|
SSL_CB_HANDSHAKE_DONE = _lib.SSL_CB_HANDSHAKE_DONE
|
|
|
|
# Taken from https://golang.org/src/crypto/x509/root_linux.go
|
|
_CERTIFICATE_FILE_LOCATIONS = [
|
|
"/etc/ssl/certs/ca-certificates.crt", # Debian/Ubuntu/Gentoo etc.
|
|
"/etc/pki/tls/certs/ca-bundle.crt", # Fedora/RHEL 6
|
|
"/etc/ssl/ca-bundle.pem", # OpenSUSE
|
|
"/etc/pki/tls/cacert.pem", # OpenELEC
|
|
"/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", # CentOS/RHEL 7
|
|
]
|
|
|
|
_CERTIFICATE_PATH_LOCATIONS = [
|
|
"/etc/ssl/certs", # SLES10/SLES11
|
|
]
|
|
|
|
# These values are compared to output from cffi's ffi.string so they must be
|
|
# byte strings.
|
|
_CRYPTOGRAPHY_MANYLINUX1_CA_DIR = b"/opt/pyca/cryptography/openssl/certs"
|
|
_CRYPTOGRAPHY_MANYLINUX1_CA_FILE = b"/opt/pyca/cryptography/openssl/cert.pem"
|
|
|
|
|
|
class Error(Exception):
|
|
"""
|
|
An error occurred in an `OpenSSL.SSL` API.
|
|
"""
|
|
|
|
|
|
_raise_current_error = partial(_exception_from_error_queue, Error)
|
|
_openssl_assert = _make_assert(Error)
|
|
|
|
|
|
class WantReadError(Error):
|
|
pass
|
|
|
|
|
|
class WantWriteError(Error):
|
|
pass
|
|
|
|
|
|
class WantX509LookupError(Error):
|
|
pass
|
|
|
|
|
|
class ZeroReturnError(Error):
|
|
pass
|
|
|
|
|
|
class SysCallError(Error):
|
|
pass
|
|
|
|
|
|
class _CallbackExceptionHelper(object):
|
|
"""
|
|
A base class for wrapper classes that allow for intelligent exception
|
|
handling in OpenSSL callbacks.
|
|
|
|
:ivar list _problems: Any exceptions that occurred while executing in a
|
|
context where they could not be raised in the normal way. Typically
|
|
this is because OpenSSL has called into some Python code and requires a
|
|
return value. The exceptions are saved to be raised later when it is
|
|
possible to do so.
|
|
"""
|
|
|
|
def __init__(self):
|
|
self._problems = []
|
|
|
|
def raise_if_problem(self):
|
|
"""
|
|
Raise an exception from the OpenSSL error queue or that was previously
|
|
captured whe running a callback.
|
|
"""
|
|
if self._problems:
|
|
try:
|
|
_raise_current_error()
|
|
except Error:
|
|
pass
|
|
raise self._problems.pop(0)
|
|
|
|
|
|
class _VerifyHelper(_CallbackExceptionHelper):
|
|
"""
|
|
Wrap a callback such that it can be used as a certificate verification
|
|
callback.
|
|
"""
|
|
|
|
def __init__(self, callback):
|
|
_CallbackExceptionHelper.__init__(self)
|
|
|
|
@wraps(callback)
|
|
def wrapper(ok, store_ctx):
|
|
x509 = _lib.X509_STORE_CTX_get_current_cert(store_ctx)
|
|
_lib.X509_up_ref(x509)
|
|
cert = X509._from_raw_x509_ptr(x509)
|
|
error_number = _lib.X509_STORE_CTX_get_error(store_ctx)
|
|
error_depth = _lib.X509_STORE_CTX_get_error_depth(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, cert, error_number, error_depth, ok
|
|
)
|
|
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.
|
|
"""
|
|
|
|
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
|
|
)
|
|
|
|
|
|
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
|
|
)
|
|
|
|
|
|
class _ALPNSelectHelper(_CallbackExceptionHelper):
|
|
"""
|
|
Wrap a callback such that it can be used as an ALPN 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 made up of multiple
|
|
# length-prefixed bytestrings. We need to split that into a
|
|
# list.
|
|
instr = _ffi.buffer(in_, inlen)[:]
|
|
protolist = []
|
|
while instr:
|
|
encoded_len = indexbytes(instr, 0)
|
|
proto = instr[1:encoded_len + 1]
|
|
protolist.append(proto)
|
|
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.")
|
|
|
|
# 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),
|
|
]
|
|
outlen[0] = conn._alpn_select_callback_args[0][0]
|
|
out[0] = conn._alpn_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
|
|
)
|
|
|
|
|
|
class _OCSPServerCallbackHelper(_CallbackExceptionHelper):
|
|
"""
|
|
Wrap a callback such that it can be used as an OCSP callback for the server
|
|
side.
|
|
|
|
Annoyingly, OpenSSL defines one OCSP callback but uses it in two different
|
|
ways. For servers, that callback is expected to retrieve some OCSP data and
|
|
hand it to OpenSSL, and may return only SSL_TLSEXT_ERR_OK,
|
|
SSL_TLSEXT_ERR_FATAL, and SSL_TLSEXT_ERR_NOACK. For clients, that callback
|
|
is expected to check the OCSP data, and returns a negative value on error,
|
|
0 if the response is not acceptable, or positive if it is. These are
|
|
mutually exclusive return code behaviours, and they mean that we need two
|
|
helpers so that we always return an appropriate error code if the user's
|
|
code throws an exception.
|
|
|
|
Given that we have to have two helpers anyway, these helpers are a bit more
|
|
helpery than most: specifically, they hide a few more of the OpenSSL
|
|
functions so that the user has an easier time writing these callbacks.
|
|
|
|
This helper implements the server side.
|
|
"""
|
|
|
|
def __init__(self, callback):
|
|
_CallbackExceptionHelper.__init__(self)
|
|
|
|
@wraps(callback)
|
|
def wrapper(ssl, cdata):
|
|
try:
|
|
conn = Connection._reverse_mapping[ssl]
|
|
|
|
# Extract the data if any was provided.
|
|
if cdata != _ffi.NULL:
|
|
data = _ffi.from_handle(cdata)
|
|
else:
|
|
data = None
|
|
|
|
# Call the callback.
|
|
ocsp_data = callback(conn, data)
|
|
|
|
if not isinstance(ocsp_data, _binary_type):
|
|
raise TypeError("OCSP callback must return a bytestring.")
|
|
|
|
# If the OCSP data was provided, we will pass it to OpenSSL.
|
|
# However, we have an early exit here: if no OCSP data was
|
|
# provided we will just exit out and tell OpenSSL that there
|
|
# is nothing to do.
|
|
if not ocsp_data:
|
|
return 3 # SSL_TLSEXT_ERR_NOACK
|
|
|
|
# Pass the data to OpenSSL. Insanely, OpenSSL doesn't make a
|
|
# private copy of this data, so we need to keep it alive, but
|
|
# it *does* want to free it itself if it gets replaced. This
|
|
# somewhat bonkers behaviour means we need to use
|
|
# OPENSSL_malloc directly, which is a pain in the butt to work
|
|
# with. It's ok for us to "leak" the memory here because
|
|
# OpenSSL now owns it and will free it.
|
|
ocsp_data_length = len(ocsp_data)
|
|
data_ptr = _lib.OPENSSL_malloc(ocsp_data_length)
|
|
_ffi.buffer(data_ptr, ocsp_data_length)[:] = ocsp_data
|
|
|
|
_lib.SSL_set_tlsext_status_ocsp_resp(
|
|
ssl, data_ptr, ocsp_data_length
|
|
)
|
|
|
|
return 0
|
|
except Exception as e:
|
|
self._problems.append(e)
|
|
return 2 # SSL_TLSEXT_ERR_ALERT_FATAL
|
|
|
|
self.callback = _ffi.callback("int (*)(SSL *, void *)", wrapper)
|
|
|
|
|
|
class _OCSPClientCallbackHelper(_CallbackExceptionHelper):
|
|
"""
|
|
Wrap a callback such that it can be used as an OCSP callback for the client
|
|
side.
|
|
|
|
Annoyingly, OpenSSL defines one OCSP callback but uses it in two different
|
|
ways. For servers, that callback is expected to retrieve some OCSP data and
|
|
hand it to OpenSSL, and may return only SSL_TLSEXT_ERR_OK,
|
|
SSL_TLSEXT_ERR_FATAL, and SSL_TLSEXT_ERR_NOACK. For clients, that callback
|
|
is expected to check the OCSP data, and returns a negative value on error,
|
|
0 if the response is not acceptable, or positive if it is. These are
|
|
mutually exclusive return code behaviours, and they mean that we need two
|
|
helpers so that we always return an appropriate error code if the user's
|
|
code throws an exception.
|
|
|
|
Given that we have to have two helpers anyway, these helpers are a bit more
|
|
helpery than most: specifically, they hide a few more of the OpenSSL
|
|
functions so that the user has an easier time writing these callbacks.
|
|
|
|
This helper implements the client side.
|
|
"""
|
|
|
|
def __init__(self, callback):
|
|
_CallbackExceptionHelper.__init__(self)
|
|
|
|
@wraps(callback)
|
|
def wrapper(ssl, cdata):
|
|
try:
|
|
conn = Connection._reverse_mapping[ssl]
|
|
|
|
# Extract the data if any was provided.
|
|
if cdata != _ffi.NULL:
|
|
data = _ffi.from_handle(cdata)
|
|
else:
|
|
data = None
|
|
|
|
# Get the OCSP data.
|
|
ocsp_ptr = _ffi.new("unsigned char **")
|
|
ocsp_len = _lib.SSL_get_tlsext_status_ocsp_resp(ssl, ocsp_ptr)
|
|
if ocsp_len < 0:
|
|
# No OCSP data.
|
|
ocsp_data = b''
|
|
else:
|
|
# Copy the OCSP data, then pass it to the callback.
|
|
ocsp_data = _ffi.buffer(ocsp_ptr[0], ocsp_len)[:]
|
|
|
|
valid = callback(conn, ocsp_data, data)
|
|
|
|
# Return 1 on success or 0 on error.
|
|
return int(bool(valid))
|
|
|
|
except Exception as e:
|
|
self._problems.append(e)
|
|
# Return negative value if an exception is hit.
|
|
return -1
|
|
|
|
self.callback = _ffi.callback("int (*)(SSL *, void *)", wrapper)
|
|
|
|
|
|
def _asFileDescriptor(obj):
|
|
fd = None
|
|
if not isinstance(obj, integer_types):
|
|
meth = getattr(obj, "fileno", None)
|
|
if meth is not None:
|
|
obj = meth()
|
|
|
|
if isinstance(obj, integer_types):
|
|
fd = obj
|
|
|
|
if not isinstance(fd, integer_types):
|
|
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,))
|
|
|
|
return fd
|
|
|
|
|
|
def SSLeay_version(type):
|
|
"""
|
|
Return a string describing the version of OpenSSL in use.
|
|
|
|
:param type: One of the :const:`SSLEAY_` constants defined in this module.
|
|
"""
|
|
return _ffi.string(_lib.SSLeay_version(type))
|
|
|
|
|
|
def _make_requires(flag, error):
|
|
"""
|
|
Builds a decorator that ensures that functions that rely on OpenSSL
|
|
functions that are not present in this build raise NotImplementedError,
|
|
rather than AttributeError coming out of cryptography.
|
|
|
|
:param flag: A cryptography flag that guards the functions, e.g.
|
|
``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
|
|
|
|
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"
|
|
)
|
|
|
|
|
|
class Session(object):
|
|
"""
|
|
A class representing an SSL session. A session defines certain connection
|
|
parameters which may be re-used to speed up the setup of subsequent
|
|
connections.
|
|
|
|
.. versionadded:: 0.14
|
|
"""
|
|
pass
|
|
|
|
|
|
class Context(object):
|
|
"""
|
|
:class:`OpenSSL.SSL.Context` instances define the parameters for setting
|
|
up new SSL connections.
|
|
|
|
:param method: One of SSLv2_METHOD, SSLv3_METHOD, SSLv23_METHOD, or
|
|
TLSv1_METHOD.
|
|
"""
|
|
_methods = {
|
|
SSLv2_METHOD: "SSLv2_method",
|
|
SSLv3_METHOD: "SSLv3_method",
|
|
SSLv23_METHOD: "SSLv23_method",
|
|
TLSv1_METHOD: "TLSv1_method",
|
|
TLSv1_1_METHOD: "TLSv1_1_method",
|
|
TLSv1_2_METHOD: "TLSv1_2_method",
|
|
}
|
|
_methods = dict(
|
|
(identifier, getattr(_lib, name))
|
|
for (identifier, name) in _methods.items()
|
|
if getattr(_lib, name, None) is not None)
|
|
|
|
def __init__(self, method):
|
|
if not isinstance(method, integer_types):
|
|
raise TypeError("method must be an integer")
|
|
|
|
try:
|
|
method_func = self._methods[method]
|
|
except KeyError:
|
|
raise ValueError("No such protocol")
|
|
|
|
method_obj = method_func()
|
|
_openssl_assert(method_obj != _ffi.NULL)
|
|
|
|
context = _lib.SSL_CTX_new(method_obj)
|
|
_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
|
|
|
|
self._context = context
|
|
self._passphrase_helper = None
|
|
self._passphrase_callback = None
|
|
self._passphrase_userdata = None
|
|
self._verify_helper = None
|
|
self._verify_callback = None
|
|
self._info_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
|
|
self._ocsp_callback = None
|
|
self._ocsp_data = None
|
|
|
|
self.set_mode(_lib.SSL_MODE_ENABLE_PARTIAL_WRITE)
|
|
|
|
def load_verify_locations(self, cafile, capath=None):
|
|
"""
|
|
Let SSL know where we can find trusted certificates for the certificate
|
|
chain. Note that the certificates have to be in PEM format.
|
|
|
|
If capath is passed, it must be a directory prepared using the
|
|
``c_rehash`` tool included with OpenSSL. Either, but not both, of
|
|
*pemfile* or *capath* may be :data:`None`.
|
|
|
|
:param cafile: In which file we can find the certificates (``bytes`` or
|
|
``unicode``).
|
|
:param capath: In which directory we can find the certificates
|
|
(``bytes`` or ``unicode``).
|
|
|
|
:return: None
|
|
"""
|
|
if cafile is None:
|
|
cafile = _ffi.NULL
|
|
else:
|
|
cafile = _path_string(cafile)
|
|
|
|
if capath is None:
|
|
capath = _ffi.NULL
|
|
else:
|
|
capath = _path_string(capath)
|
|
|
|
load_result = _lib.SSL_CTX_load_verify_locations(
|
|
self._context, cafile, capath
|
|
)
|
|
if not load_result:
|
|
_raise_current_error()
|
|
|
|
def _wrap_callback(self, callback):
|
|
@wraps(callback)
|
|
def wrapper(size, verify, userdata):
|
|
return callback(size, verify, self._passphrase_userdata)
|
|
return _PassphraseHelper(
|
|
FILETYPE_PEM, wrapper, more_args=True, truncate=True)
|
|
|
|
def set_passwd_cb(self, callback, userdata=None):
|
|
"""
|
|
Set the passphrase callback. This function will be called
|
|
when a private key with a passphrase is loaded.
|
|
|
|
:param callback: The Python callback to use. This must accept three
|
|
positional arguments. First, an integer giving the maximum length
|
|
of the passphrase it may return. If the returned passphrase is
|
|
longer than this, it will be truncated. Second, a boolean value
|
|
which will be true if the user should be prompted for the
|
|
passphrase twice and the callback should verify that the two values
|
|
supplied are equal. Third, the value given as the *userdata*
|
|
parameter to :meth:`set_passwd_cb`. The *callback* must return
|
|
a byte string. If an error occurs, *callback* should return a false
|
|
value (e.g. an empty string).
|
|
: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
|
|
_lib.SSL_CTX_set_default_passwd_cb(
|
|
self._context, self._passphrase_callback)
|
|
self._passphrase_userdata = userdata
|
|
|
|
def set_default_verify_paths(self):
|
|
"""
|
|
Specify that the platform provided CA certificates are to be used for
|
|
verification purposes. This method has some caveats related to the
|
|
binary wheels that cryptography (pyOpenSSL's primary dependency) ships:
|
|
|
|
* macOS will only load certificates using this method if the user has
|
|
the ``openssl@1.1`` `Homebrew <https://brew.sh>`_ formula installed
|
|
in the default location.
|
|
* Windows will not work.
|
|
* manylinux1 cryptography wheels will work on most common Linux
|
|
distributions in pyOpenSSL 17.1.0 and above. pyOpenSSL detects the
|
|
manylinux1 wheel and attempts to load roots via a fallback path.
|
|
|
|
:return: None
|
|
"""
|
|
# SSL_CTX_set_default_verify_paths will attempt to load certs from
|
|
# both a cafile and capath that are set at compile time. However,
|
|
# it will first check environment variables and, if present, load
|
|
# those paths instead
|
|
set_result = _lib.SSL_CTX_set_default_verify_paths(self._context)
|
|
_openssl_assert(set_result == 1)
|
|
# After attempting to set default_verify_paths we need to know whether
|
|
# to go down the fallback path.
|
|
# 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")
|
|
file_env_var = _ffi.string(
|
|
_lib.X509_get_default_cert_file_env()
|
|
).decode("ascii")
|
|
if not self._check_env_vars_set(dir_env_var, file_env_var):
|
|
default_dir = _ffi.string(_lib.X509_get_default_cert_dir())
|
|
default_file = _ffi.string(_lib.X509_get_default_cert_file())
|
|
# Now we check to see if the default_dir and default_file are set
|
|
# 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
|
|
):
|
|
# This is manylinux1, let's load our fallback paths
|
|
self._fallback_default_verify_paths(
|
|
_CERTIFICATE_FILE_LOCATIONS,
|
|
_CERTIFICATE_PATH_LOCATIONS
|
|
)
|
|
|
|
def _check_env_vars_set(self, dir_env_var, file_env_var):
|
|
"""
|
|
Check to see if the default cert dir/file environment vars are present.
|
|
|
|
:return: bool
|
|
"""
|
|
return (
|
|
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):
|
|
"""
|
|
Default verify paths are based on the compiled version of OpenSSL.
|
|
However, when pyca/cryptography is compiled as a manylinux1 wheel
|
|
that compiled location can potentially be wrong. So, like Go, we
|
|
will try a predefined set of paths and attempt to load roots
|
|
from there.
|
|
|
|
:return: None
|
|
"""
|
|
for cafile in file_path:
|
|
if os.path.isfile(cafile):
|
|
self.load_verify_locations(cafile)
|
|
break
|
|
|
|
for capath in dir_path:
|
|
if os.path.isdir(capath):
|
|
self.load_verify_locations(None, capath)
|
|
break
|
|
|
|
def use_certificate_chain_file(self, certfile):
|
|
"""
|
|
Load a certificate chain from a file.
|
|
|
|
:param certfile: The name of the certificate chain file (``bytes`` or
|
|
``unicode``). Must be PEM encoded.
|
|
|
|
:return: None
|
|
"""
|
|
certfile = _path_string(certfile)
|
|
|
|
result = _lib.SSL_CTX_use_certificate_chain_file(
|
|
self._context, certfile
|
|
)
|
|
if not result:
|
|
_raise_current_error()
|
|
|
|
def use_certificate_file(self, certfile, filetype=FILETYPE_PEM):
|
|
"""
|
|
Load a certificate from a file
|
|
|
|
:param certfile: The name of the certificate file (``bytes`` or
|
|
``unicode``).
|
|
:param filetype: (optional) The encoding of the file, which is either
|
|
:const:`FILETYPE_PEM` or :const:`FILETYPE_ASN1`. The default is
|
|
:const:`FILETYPE_PEM`.
|
|
|
|
:return: None
|
|
"""
|
|
certfile = _path_string(certfile)
|
|
if not isinstance(filetype, integer_types):
|
|
raise TypeError("filetype must be an integer")
|
|
|
|
use_result = _lib.SSL_CTX_use_certificate_file(
|
|
self._context, certfile, filetype
|
|
)
|
|
if not use_result:
|
|
_raise_current_error()
|
|
|
|
def use_certificate(self, cert):
|
|
"""
|
|
Load a certificate from a X509 object
|
|
|
|
:param cert: The X509 object
|
|
:return: None
|
|
"""
|
|
if not isinstance(cert, X509):
|
|
raise TypeError("cert must be an X509 instance")
|
|
|
|
use_result = _lib.SSL_CTX_use_certificate(self._context, cert._x509)
|
|
if not use_result:
|
|
_raise_current_error()
|
|
|
|
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 = _lib.X509_dup(certobj._x509)
|
|
add_result = _lib.SSL_CTX_add_extra_chain_cert(self._context, copy)
|
|
if not add_result:
|
|
# TODO: This is untested.
|
|
_lib.X509_free(copy)
|
|
_raise_current_error()
|
|
|
|
def _raise_passphrase_exception(self):
|
|
if self._passphrase_helper is not None:
|
|
self._passphrase_helper.raise_if_problem(Error)
|
|
|
|
_raise_current_error()
|
|
|
|
def use_privatekey_file(self, keyfile, filetype=_UNSPECIFIED):
|
|
"""
|
|
Load a private key from a file
|
|
|
|
:param keyfile: The name of the key file (``bytes`` or ``unicode``)
|
|
:param filetype: (optional) The encoding of the file, which is either
|
|
:const:`FILETYPE_PEM` or :const:`FILETYPE_ASN1`. The default is
|
|
:const:`FILETYPE_PEM`.
|
|
|
|
:return: None
|
|
"""
|
|
keyfile = _path_string(keyfile)
|
|
|
|
if filetype is _UNSPECIFIED:
|
|
filetype = FILETYPE_PEM
|
|
elif not isinstance(filetype, integer_types):
|
|
raise TypeError("filetype must be an integer")
|
|
|
|
use_result = _lib.SSL_CTX_use_PrivateKey_file(
|
|
self._context, keyfile, filetype)
|
|
if not use_result:
|
|
self._raise_passphrase_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 = _lib.SSL_CTX_use_PrivateKey(self._context, pkey._pkey)
|
|
if not use_result:
|
|
self._raise_passphrase_exception()
|
|
|
|
def check_privatekey(self):
|
|
"""
|
|
Check if the private key (loaded with :meth:`use_privatekey`) matches
|
|
the certificate (loaded with :meth:`use_certificate`)
|
|
|
|
:return: :data:`None` (raises :exc:`Error` if something's wrong)
|
|
"""
|
|
if not _lib.SSL_CTX_check_private_key(self._context):
|
|
_raise_current_error()
|
|
|
|
def load_client_ca(self, cafile):
|
|
"""
|
|
Load the trusted certificates that will be sent to the client. Does
|
|
not actually imply any of the certificates are trusted; that must be
|
|
configured separately.
|
|
|
|
:param bytes cafile: The path to a certificates file in PEM format.
|
|
:return: None
|
|
"""
|
|
ca_list = _lib.SSL_load_client_CA_file(
|
|
_text_to_bytes_and_warn("cafile", cafile)
|
|
)
|
|
_openssl_assert(ca_list != _ffi.NULL)
|
|
_lib.SSL_CTX_set_client_CA_list(self._context, ca_list)
|
|
|
|
def set_session_id(self, buf):
|
|
"""
|
|
Set the session id to *buf* within which a session can be reused for
|
|
this Context object. This is needed when doing session resumption,
|
|
because there is no way for a stored session to know which Context
|
|
object it is associated with.
|
|
|
|
:param bytes buf: The session id.
|
|
|
|
:returns: None
|
|
"""
|
|
buf = _text_to_bytes_and_warn("buf", buf)
|
|
_openssl_assert(
|
|
_lib.SSL_CTX_set_session_id_context(
|
|
self._context,
|
|
buf,
|
|
len(buf),
|
|
) == 1
|
|
)
|
|
|
|
def set_session_cache_mode(self, mode):
|
|
"""
|
|
Set the behavior of the session cache used by all connections using
|
|
this Context. The previously set mode is returned. See
|
|
:const:`SESS_CACHE_*` for details about particular modes.
|
|
|
|
:param mode: One or more of the SESS_CACHE_* flags (combine using
|
|
bitwise or)
|
|
:returns: The previously set caching mode.
|
|
|
|
.. versionadded:: 0.14
|
|
"""
|
|
if not isinstance(mode, integer_types):
|
|
raise TypeError("mode must be an integer")
|
|
|
|
return _lib.SSL_CTX_set_session_cache_mode(self._context, mode)
|
|
|
|
def get_session_cache_mode(self):
|
|
"""
|
|
Get the current session cache mode.
|
|
|
|
:returns: The currently used cache mode.
|
|
|
|
.. versionadded:: 0.14
|
|
"""
|
|
return _lib.SSL_CTX_get_session_cache_mode(self._context)
|
|
|
|
def set_verify(self, mode, callback):
|
|
"""
|
|
et 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.
|
|
: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 = _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):
|
|
"""
|
|
Set the maximum depth for the certificate chain verification that shall
|
|
be allowed for this Context 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_CTX_set_verify_depth(self._context, depth)
|
|
|
|
def get_verify_mode(self):
|
|
"""
|
|
Retrieve the Context object's verify mode, as set by
|
|
:meth:`set_verify`.
|
|
|
|
:return: The verify mode
|
|
"""
|
|
return _lib.SSL_CTX_get_verify_mode(self._context)
|
|
|
|
def get_verify_depth(self):
|
|
"""
|
|
Retrieve the Context object's verify depth, as set by
|
|
:meth:`set_verify_depth`.
|
|
|
|
:return: The verify depth
|
|
"""
|
|
return _lib.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 (``bytes`` or
|
|
``unicode``).
|
|
|
|
:return: None
|
|
"""
|
|
dhfile = _path_string(dhfile)
|
|
|
|
bio = _lib.BIO_new_file(dhfile, b"r")
|
|
if bio == _ffi.NULL:
|
|
_raise_current_error()
|
|
bio = _ffi.gc(bio, _lib.BIO_free)
|
|
|
|
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)
|
|
|
|
def set_tmp_ecdh(self, curve):
|
|
"""
|
|
Select a curve to use for ECDHE key exchange.
|
|
|
|
:param curve: A curve object to use as returned by either
|
|
:meth:`OpenSSL.crypto.get_elliptic_curve` or
|
|
:meth:`OpenSSL.crypto.get_elliptic_curves`.
|
|
|
|
:return: None
|
|
"""
|
|
_lib.SSL_CTX_set_tmp_ecdh(self._context, curve._to_EC_KEY())
|
|
|
|
def set_cipher_list(self, cipher_list):
|
|
"""
|
|
Set the list of ciphers to be used in this context.
|
|
|
|
See the OpenSSL manual for more information (e.g.
|
|
:manpage:`ciphers(1)`).
|
|
|
|
:param bytes cipher_list: An OpenSSL cipher string.
|
|
:return: None
|
|
"""
|
|
cipher_list = _text_to_bytes_and_warn("cipher_list", cipher_list)
|
|
|
|
if not isinstance(cipher_list, bytes):
|
|
raise TypeError("cipher_list must be a byte string.")
|
|
|
|
_openssl_assert(
|
|
_lib.SSL_CTX_set_cipher_list(self._context, cipher_list) == 1
|
|
)
|
|
|
|
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
|
|
|
|
.. versionadded:: 0.10
|
|
"""
|
|
name_stack = _lib.sk_X509_NAME_new_null()
|
|
_openssl_assert(name_stack != _ffi.NULL)
|
|
|
|
try:
|
|
for ca_name in certificate_authorities:
|
|
if not isinstance(ca_name, X509Name):
|
|
raise TypeError(
|
|
"client CAs must be X509Name objects, not %s "
|
|
"objects" % (
|
|
type(ca_name).__name__,
|
|
)
|
|
)
|
|
copy = _lib.X509_NAME_dup(ca_name._name)
|
|
_openssl_assert(copy != _ffi.NULL)
|
|
push_result = _lib.sk_X509_NAME_push(name_stack, copy)
|
|
if not push_result:
|
|
_lib.X509_NAME_free(copy)
|
|
_raise_current_error()
|
|
except Exception:
|
|
_lib.sk_X509_NAME_free(name_stack)
|
|
raise
|
|
|
|
_lib.SSL_CTX_set_client_CA_list(self._context, name_stack)
|
|
|
|
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
|
|
|
|
.. versionadded:: 0.10
|
|
"""
|
|
if not isinstance(certificate_authority, X509):
|
|
raise TypeError("certificate_authority must be an X509 instance")
|
|
|
|
add_result = _lib.SSL_CTX_add_client_CA(
|
|
self._context, certificate_authority._x509)
|
|
_openssl_assert(add_result == 1)
|
|
|
|
def set_timeout(self, timeout):
|
|
"""
|
|
Set the timeout for newly created sessions for this Context object to
|
|
*timeout*. The default value is 300 seconds. See the OpenSSL manual
|
|
for more information (e.g. :manpage:`SSL_CTX_set_timeout(3)`).
|
|
|
|
:param timeout: The timeout in (whole) seconds
|
|
:return: The previous session timeout
|
|
"""
|
|
if not isinstance(timeout, integer_types):
|
|
raise TypeError("timeout must be an integer")
|
|
|
|
return _lib.SSL_CTX_set_timeout(self._context, timeout)
|
|
|
|
def get_timeout(self):
|
|
"""
|
|
Retrieve session timeout, as set by :meth:`set_timeout`. The default
|
|
is 300 seconds.
|
|
|
|
:return: The session timeout
|
|
"""
|
|
return _lib.SSL_CTX_get_timeout(self._context)
|
|
|
|
def set_info_callback(self, callback):
|
|
"""
|
|
Set the information callback to *callback*. This function will be
|
|
called from time to time during SSL handshakes.
|
|
|
|
:param callback: The Python callback to use. This should take three
|
|
arguments: a Connection object and two integers. The first integer
|
|
specifies where in the SSL handshake the function was called, and
|
|
the other the return code from a (possibly failed) internal
|
|
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)
|
|
_lib.SSL_CTX_set_info_callback(self._context, self._info_callback)
|
|
|
|
def get_app_data(self):
|
|
"""
|
|
Get the application data (supplied via :meth:`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. This can be used to add
|
|
"trusted" certificates without using the
|
|
:meth:`load_verify_locations` method.
|
|
|
|
:return: A X509Store object or None if it does not have one.
|
|
"""
|
|
store = _lib.SSL_CTX_get_cert_store(self._context)
|
|
if store == _ffi.NULL:
|
|
# TODO: This is untested.
|
|
return None
|
|
|
|
pystore = X509Store.__new__(X509Store)
|
|
pystore._store = store
|
|
return pystore
|
|
|
|
def set_options(self, options):
|
|
"""
|
|
Add options. Options set before are not cleared!
|
|
This method should be used with the :const:`OP_*` constants.
|
|
|
|
:param options: The options to add.
|
|
:return: The new option bitmask.
|
|
"""
|
|
if not isinstance(options, integer_types):
|
|
raise TypeError("options must be an integer")
|
|
|
|
return _lib.SSL_CTX_set_options(self._context, options)
|
|
|
|
def set_mode(self, mode):
|
|
"""
|
|
Add modes via bitmask. Modes set before are not cleared! This method
|
|
should be used with the :const:`MODE_*` constants.
|
|
|
|
:param mode: The mode to add.
|
|
:return: The new mode bitmask.
|
|
"""
|
|
if not isinstance(mode, integer_types):
|
|
raise TypeError("mode must be an integer")
|
|
|
|
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
|
|
name.
|
|
|
|
:param callback: The callback function. It will be invoked with one
|
|
argument, the Connection instance.
|
|
|
|
.. versionadded:: 0.13
|
|
"""
|
|
@wraps(callback)
|
|
def wrapper(ssl, alert, arg):
|
|
callback(Connection._reverse_mapping[ssl])
|
|
return 0
|
|
|
|
self._tlsext_servername_callback = _ffi.callback(
|
|
"int (*)(const SSL *, int *, void *)", wrapper)
|
|
_lib.SSL_CTX_set_tlsext_servername_callback(
|
|
self._context, self._tlsext_servername_callback)
|
|
|
|
def set_tlsext_use_srtp(self, profiles):
|
|
"""
|
|
Enable support for negotiating SRTP keying material.
|
|
|
|
:param bytes profiles: A colon delimited list of protection profile
|
|
names, like ``b'SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32'``.
|
|
:return: None
|
|
"""
|
|
if not isinstance(profiles, bytes):
|
|
raise TypeError("profiles must be a byte string.")
|
|
|
|
_openssl_assert(
|
|
_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):
|
|
"""
|
|
Specify the protocols that the client is prepared to speak after the
|
|
TLS connection has been negotiated using Application Layer Protocol
|
|
Negotiation.
|
|
|
|
:param protos: A list of the protocols to be offered to the server.
|
|
This list should be a Python list of bytestrings representing the
|
|
protocols to offer, e.g. ``[b'http/1.1', b'spdy/2']``.
|
|
"""
|
|
# Take the list of protocols and join them together, prefixing them
|
|
# with their lengths.
|
|
protostr = b''.join(
|
|
chain.from_iterable((int2byte(len(p)), p) for p in protos)
|
|
)
|
|
|
|
# Build a C string from the list. We don't need to save this off
|
|
# because OpenSSL immediately copies the data out.
|
|
input_str = _ffi.new("unsigned char[]", protostr)
|
|
_lib.SSL_CTX_set_alpn_protos(self._context, input_str, len(protostr))
|
|
|
|
@_requires_alpn
|
|
def set_alpn_select_callback(self, callback):
|
|
"""
|
|
Specify a callback function that will be called on the server when a
|
|
client offers protocols using ALPN.
|
|
|
|
: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.
|
|
"""
|
|
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)
|
|
|
|
def _set_ocsp_callback(self, helper, data):
|
|
"""
|
|
This internal helper does the common work for
|
|
``set_ocsp_server_callback`` and ``set_ocsp_client_callback``, which is
|
|
almost all of it.
|
|
"""
|
|
self._ocsp_helper = helper
|
|
self._ocsp_callback = helper.callback
|
|
if data is None:
|
|
self._ocsp_data = _ffi.NULL
|
|
else:
|
|
self._ocsp_data = _ffi.new_handle(data)
|
|
|
|
rc = _lib.SSL_CTX_set_tlsext_status_cb(
|
|
self._context, self._ocsp_callback
|
|
)
|
|
_openssl_assert(rc == 1)
|
|
rc = _lib.SSL_CTX_set_tlsext_status_arg(self._context, self._ocsp_data)
|
|
_openssl_assert(rc == 1)
|
|
|
|
def set_ocsp_server_callback(self, callback, data=None):
|
|
"""
|
|
Set a callback to provide OCSP data to be stapled to the TLS handshake
|
|
on the server side.
|
|
|
|
:param callback: The callback function. It will be invoked with two
|
|
arguments: the Connection, and the optional arbitrary data you have
|
|
provided. The callback must return a bytestring that contains the
|
|
OCSP data to staple to the handshake. If no OCSP data is available
|
|
for this connection, return the empty bytestring.
|
|
:param data: Some opaque data that will be passed into the callback
|
|
function when called. This can be used to avoid needing to do
|
|
complex data lookups or to keep track of what context is being
|
|
used. This parameter is optional.
|
|
"""
|
|
helper = _OCSPServerCallbackHelper(callback)
|
|
self._set_ocsp_callback(helper, data)
|
|
|
|
def set_ocsp_client_callback(self, callback, data=None):
|
|
"""
|
|
Set a callback to validate OCSP data stapled to the TLS handshake on
|
|
the client side.
|
|
|
|
:param callback: The callback function. It will be invoked with three
|
|
arguments: the Connection, a bytestring containing the stapled OCSP
|
|
assertion, and the optional arbitrary data you have provided. The
|
|
callback must return a boolean that indicates the result of
|
|
validating the OCSP data: ``True`` if the OCSP data is valid and
|
|
the certificate can be trusted, or ``False`` if either the OCSP
|
|
data is invalid or the certificate has been revoked.
|
|
:param data: Some opaque data that will be passed into the callback
|
|
function when called. This can be used to avoid needing to do
|
|
complex data lookups or to keep track of what context is being
|
|
used. This parameter is optional.
|
|
"""
|
|
helper = _OCSPClientCallbackHelper(callback)
|
|
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):
|
|
"""
|
|
Create a new Connection object, using the given OpenSSL.SSL.Context
|
|
instance and socket.
|
|
|
|
:param context: An SSL Context to use for this connection
|
|
:param socket: The socket to use for transport layer
|
|
"""
|
|
if not isinstance(context, Context):
|
|
raise TypeError("context must be a Context instance")
|
|
|
|
ssl = _lib.SSL_new(context._context)
|
|
self._ssl = _ffi.gc(ssl, _lib.SSL_free)
|
|
# We set SSL_MODE_AUTO_RETRY to handle situations where OpenSSL returns
|
|
# an SSL_ERROR_WANT_READ when processing a non-application data packet
|
|
# even though there is still data on the underlying transport.
|
|
# See https://github.com/openssl/openssl/issues/6234 for more details.
|
|
_lib.SSL_set_mode(self._ssl, _lib.SSL_MODE_AUTO_RETRY)
|
|
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
|
|
|
|
self._reverse_mapping[self._ssl] = self
|
|
|
|
if socket is None:
|
|
self._socket = None
|
|
# Don't set up any gc for these, SSL_free will take care of them.
|
|
self._into_ssl = _lib.BIO_new(_lib.BIO_s_mem())
|
|
_openssl_assert(self._into_ssl != _ffi.NULL)
|
|
|
|
self._from_ssl = _lib.BIO_new(_lib.BIO_s_mem())
|
|
_openssl_assert(self._from_ssl != _ffi.NULL)
|
|
|
|
_lib.SSL_set_bio(self._ssl, self._into_ssl, self._from_ssl)
|
|
else:
|
|
self._into_ssl = None
|
|
self._from_ssl = None
|
|
self._socket = socket
|
|
set_result = _lib.SSL_set_fd(
|
|
self._ssl, _asFileDescriptor(self._socket))
|
|
_openssl_assert(set_result == 1)
|
|
|
|
def __getattr__(self, name):
|
|
"""
|
|
Look up attributes on the wrapped socket object if they are not found
|
|
on the Connection object.
|
|
"""
|
|
if self._socket is None:
|
|
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:
|
|
self._context._ocsp_helper.raise_if_problem()
|
|
|
|
error = _lib.SSL_get_error(ssl, result)
|
|
if error == _lib.SSL_ERROR_WANT_READ:
|
|
raise WantReadError()
|
|
elif error == _lib.SSL_ERROR_WANT_WRITE:
|
|
raise WantWriteError()
|
|
elif error == _lib.SSL_ERROR_ZERO_RETURN:
|
|
raise ZeroReturnError()
|
|
elif error == _lib.SSL_ERROR_WANT_X509_LOOKUP:
|
|
# TODO: This is untested.
|
|
raise WantX509LookupError()
|
|
elif error == _lib.SSL_ERROR_SYSCALL:
|
|
if _lib.ERR_peek_error() == 0:
|
|
if result < 0:
|
|
if platform == "win32":
|
|
errno = _ffi.getwinerror()[0]
|
|
else:
|
|
errno = _ffi.errno
|
|
|
|
if errno != 0:
|
|
raise SysCallError(errno, errorcode.get(errno))
|
|
raise SysCallError(-1, "Unexpected EOF")
|
|
else:
|
|
# TODO: This is untested.
|
|
_raise_current_error()
|
|
elif error == _lib.SSL_ERROR_NONE:
|
|
pass
|
|
else:
|
|
_raise_current_error()
|
|
|
|
def get_context(self):
|
|
"""
|
|
Retrieve the :class:`Context` object associated with this
|
|
:class:`Connection`.
|
|
"""
|
|
return self._context
|
|
|
|
def set_context(self, context):
|
|
"""
|
|
Switch this connection to a new session context.
|
|
|
|
:param context: A :class:`Context` instance giving the new session
|
|
context to use.
|
|
"""
|
|
if not isinstance(context, Context):
|
|
raise TypeError("context must be a Context instance")
|
|
|
|
_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
|
|
message, or None if there wasn't one.
|
|
|
|
:return: A byte string giving the server name or :data:`None`.
|
|
|
|
.. versionadded:: 0.13
|
|
"""
|
|
name = _lib.SSL_get_servername(
|
|
self._ssl, _lib.TLSEXT_NAMETYPE_host_name
|
|
)
|
|
if name == _ffi.NULL:
|
|
return None
|
|
|
|
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.
|
|
|
|
:param name: A byte string giving the name.
|
|
|
|
.. versionadded:: 0.13
|
|
"""
|
|
if not isinstance(name, bytes):
|
|
raise TypeError("name must be a byte string")
|
|
elif b"\0" in name:
|
|
raise TypeError("name must not contain NUL byte")
|
|
|
|
# XXX I guess this can fail sometimes?
|
|
_lib.SSL_set_tlsext_host_name(self._ssl, name)
|
|
|
|
def pending(self):
|
|
"""
|
|
Get the number of bytes that can be safely read from the SSL buffer
|
|
(**not** the underlying transport buffer).
|
|
|
|
:return: The number of bytes available in the receive buffer.
|
|
"""
|
|
return _lib.SSL_pending(self._ssl)
|
|
|
|
def send(self, buf, flags=0):
|
|
"""
|
|
Send data on the connection. NOTE: If you get one of the WantRead,
|
|
WantWrite or WantX509Lookup exceptions on this, you have to call the
|
|
method again with the SAME buffer.
|
|
|
|
:param buf: The string, buffer or memoryview to send
|
|
:param flags: (optional) Included for compatibility with the socket
|
|
API, the value is ignored
|
|
:return: The number of bytes written
|
|
"""
|
|
# 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.")
|
|
|
|
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):
|
|
"""
|
|
Send "all" data on the connection. This calls send() repeatedly until
|
|
all data is sent. If an error occurs, it's impossible to tell how much
|
|
data has been sent.
|
|
|
|
:param buf: The string, buffer or memoryview to send
|
|
:param flags: (optional) Included for compatibility with the socket
|
|
API, the value is ignored
|
|
:return: The number of bytes written
|
|
"""
|
|
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
|
|
|
|
def recv(self, bufsiz, flags=None):
|
|
"""
|
|
Receive data on the connection.
|
|
|
|
:param bufsiz: The maximum number of bytes to read
|
|
:param flags: (optional) The only supported flag is ``MSG_PEEK``,
|
|
all other flags are ignored.
|
|
:return: The string read from the Connection
|
|
"""
|
|
buf = _no_zero_allocator("char[]", bufsiz)
|
|
if flags is not None and flags & socket.MSG_PEEK:
|
|
result = _lib.SSL_peek(self._ssl, buf, bufsiz)
|
|
else:
|
|
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):
|
|
"""
|
|
Receive data on the connection and copy it directly into the provided
|
|
buffer, rather than creating a new string.
|
|
|
|
:param buffer: The buffer to copy into.
|
|
:param nbytes: (optional) The maximum number of bytes to read into the
|
|
buffer. If not present, defaults to the size of the buffer. If
|
|
larger than the size of the buffer, is reduced to the size of the
|
|
buffer.
|
|
:param flags: (optional) The only supported flag is ``MSG_PEEK``,
|
|
all other flags are ignored.
|
|
:return: The number of bytes read into the buffer.
|
|
"""
|
|
if nbytes is None:
|
|
nbytes = len(buffer)
|
|
else:
|
|
nbytes = min(nbytes, len(buffer))
|
|
|
|
# We need to create a temporary buffer. This is annoying, it would be
|
|
# better if we could pass memoryviews straight into the SSL_read call,
|
|
# but right now we can't. Revisit this if CFFI gets that ability.
|
|
buf = _no_zero_allocator("char[]", nbytes)
|
|
if flags is not None and flags & socket.MSG_PEEK:
|
|
result = _lib.SSL_peek(self._ssl, buf, nbytes)
|
|
else:
|
|
result = _lib.SSL_read(self._ssl, buf, nbytes)
|
|
self._raise_ssl_error(self._ssl, result)
|
|
|
|
# This strange line is all to avoid a memory copy. The buffer protocol
|
|
# should allow us to assign a CFFI buffer to the LHS of this line, but
|
|
# on CPython 3.3+ that segfaults. As a workaround, we can temporarily
|
|
# wrap it in a memoryview.
|
|
buffer[:result] = memoryview(_ffi.buffer(buf, result))
|
|
|
|
return result
|
|
|
|
def _handle_bio_errors(self, bio, result):
|
|
if _lib.BIO_should_retry(bio):
|
|
if _lib.BIO_should_read(bio):
|
|
raise WantReadError()
|
|
elif _lib.BIO_should_write(bio):
|
|
# TODO: This is untested.
|
|
raise WantWriteError()
|
|
elif _lib.BIO_should_io_special(bio):
|
|
# TODO: This is untested. I think io_special means the socket
|
|
# BIO has a not-yet connected socket.
|
|
raise ValueError("BIO_should_io_special")
|
|
else:
|
|
# TODO: This is untested.
|
|
raise ValueError("unknown bio failure")
|
|
else:
|
|
# TODO: This is untested.
|
|
_raise_current_error()
|
|
|
|
def bio_read(self, bufsiz):
|
|
"""
|
|
If the Connection was created with a memory BIO, this method can be
|
|
used to read bytes from the write end of that memory BIO. Many
|
|
Connection methods will add bytes which must be read in this manner or
|
|
the buffer will eventually fill up and the Connection will be able to
|
|
take no further actions.
|
|
|
|
:param bufsiz: The maximum number of bytes to read
|
|
:return: The string read.
|
|
"""
|
|
if self._from_ssl is None:
|
|
raise TypeError("Connection sock was not None")
|
|
|
|
if not isinstance(bufsiz, integer_types):
|
|
raise TypeError("bufsiz must be an integer")
|
|
|
|
buf = _no_zero_allocator("char[]", bufsiz)
|
|
result = _lib.BIO_read(self._from_ssl, buf, bufsiz)
|
|
if result <= 0:
|
|
self._handle_bio_errors(self._from_ssl, result)
|
|
|
|
return _ffi.buffer(buf, result)[:]
|
|
|
|
def bio_write(self, buf):
|
|
"""
|
|
If the Connection was created with a memory BIO, this method can be
|
|
used to add bytes to the read end of that memory BIO. The Connection
|
|
can then read the bytes (for example, in response to a call to
|
|
:meth:`recv`).
|
|
|
|
:param buf: The string to put into the memory BIO.
|
|
:return: The number of bytes written
|
|
"""
|
|
buf = _text_to_bytes_and_warn("buf", buf)
|
|
|
|
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
|
|
|
|
def renegotiate(self):
|
|
"""
|
|
Renegotiate the session.
|
|
|
|
:return: True if the renegotiation can be started, False otherwise
|
|
:rtype: bool
|
|
"""
|
|
if not self.renegotiate_pending():
|
|
_openssl_assert(_lib.SSL_renegotiate(self._ssl) == 1)
|
|
return True
|
|
return False
|
|
|
|
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
|
|
raise the same exceptions as :meth:`send` and :meth:`recv`.
|
|
|
|
:return: None.
|
|
"""
|
|
result = _lib.SSL_do_handshake(self._ssl)
|
|
self._raise_ssl_error(self._ssl, result)
|
|
|
|
def renegotiate_pending(self):
|
|
"""
|
|
Check if there's a renegotiation in progress, it will return False once
|
|
a renegotiation is finished.
|
|
|
|
:return: Whether there's a renegotiation in progress
|
|
:rtype: bool
|
|
"""
|
|
return _lib.SSL_renegotiate_pending(self._ssl) == 1
|
|
|
|
def total_renegotiations(self):
|
|
"""
|
|
Find out the total number of renegotiations.
|
|
|
|
:return: The number of renegotiations.
|
|
:rtype: int
|
|
"""
|
|
return _lib.SSL_total_renegotiations(self._ssl)
|
|
|
|
def connect(self, addr):
|
|
"""
|
|
Call the :meth:`connect` method of the underlying socket and set up SSL
|
|
on the socket, using the :class:`Context` object supplied to this
|
|
:class:`Connection` object at creation.
|
|
|
|
:param addr: A remote address
|
|
:return: What the socket's connect method returns
|
|
"""
|
|
_lib.SSL_set_connect_state(self._ssl)
|
|
return self._socket.connect(addr)
|
|
|
|
def connect_ex(self, addr):
|
|
"""
|
|
Call the :meth:`connect_ex` method of the underlying socket and set up
|
|
SSL on the socket, using the Context object supplied to this Connection
|
|
object at creation. Note that if the :meth:`connect_ex` method of the
|
|
socket doesn't return 0, SSL won't be initialized.
|
|
|
|
:param addr: A remove address
|
|
:return: What the socket's connect_ex method returns
|
|
"""
|
|
connect_ex = self._socket.connect_ex
|
|
self.set_connect_state()
|
|
return connect_ex(addr)
|
|
|
|
def accept(self):
|
|
"""
|
|
Call the :meth:`accept` method of the underlying socket and set up SSL
|
|
on the returned socket, using the Context object supplied to this
|
|
:class:`Connection` object at creation.
|
|
|
|
:return: A *(conn, addr)* pair where *conn* is the new
|
|
:class:`Connection` object created, and *address* is as returned by
|
|
the socket's :meth:`accept`.
|
|
"""
|
|
client, addr = self._socket.accept()
|
|
conn = Connection(self._context, client)
|
|
conn.set_accept_state()
|
|
return (conn, addr)
|
|
|
|
def bio_shutdown(self):
|
|
"""
|
|
If the Connection was created with a memory BIO, this method can be
|
|
used to indicate that *end of file* has been reached on the read end of
|
|
that memory BIO.
|
|
|
|
:return: None
|
|
"""
|
|
if self._from_ssl is None:
|
|
raise TypeError("Connection sock was not None")
|
|
|
|
_lib.BIO_set_mem_eof_return(self._into_ssl, 0)
|
|
|
|
def shutdown(self):
|
|
"""
|
|
Send the shutdown message to the Connection.
|
|
|
|
:return: True if the shutdown completed successfully (i.e. both sides
|
|
have sent closure alerts), False otherwise (in which case you
|
|
call :meth:`recv` or :meth:`send` when the connection becomes
|
|
readable/writeable).
|
|
"""
|
|
result = _lib.SSL_shutdown(self._ssl)
|
|
if result < 0:
|
|
self._raise_ssl_error(self._ssl, result)
|
|
elif result > 0:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def get_cipher_list(self):
|
|
"""
|
|
Retrieve the list of ciphers used by the Connection object.
|
|
|
|
:return: A list of native cipher strings.
|
|
"""
|
|
ciphers = []
|
|
for i in count():
|
|
result = _lib.SSL_get_cipher_list(self._ssl, i)
|
|
if result == _ffi.NULL:
|
|
break
|
|
ciphers.append(_native(_ffi.string(result)))
|
|
return ciphers
|
|
|
|
def get_client_ca_list(self):
|
|
"""
|
|
Get CAs whose certificates are suggested for client authentication.
|
|
|
|
:return: If this is a server connection, the list of certificate
|
|
authorities that will be sent or has been sent to the client, as
|
|
controlled by this :class:`Connection`'s :class:`Context`.
|
|
|
|
If this is a client connection, the list will be empty until the
|
|
connection with the server is established.
|
|
|
|
.. versionadded:: 0.10
|
|
"""
|
|
ca_names = _lib.SSL_get_client_CA_list(self._ssl)
|
|
if ca_names == _ffi.NULL:
|
|
# TODO: This is untested.
|
|
return []
|
|
|
|
result = []
|
|
for i in range(_lib.sk_X509_NAME_num(ca_names)):
|
|
name = _lib.sk_X509_NAME_value(ca_names, i)
|
|
copy = _lib.X509_NAME_dup(name)
|
|
_openssl_assert(copy != _ffi.NULL)
|
|
|
|
pyname = X509Name.__new__(X509Name)
|
|
pyname._name = _ffi.gc(copy, _lib.X509_NAME_free)
|
|
result.append(pyname)
|
|
return result
|
|
|
|
def makefile(self, *args, **kwargs):
|
|
"""
|
|
The makefile() method is not implemented, since there is no dup
|
|
semantics for SSL connections
|
|
|
|
:raise: NotImplementedError
|
|
"""
|
|
raise NotImplementedError(
|
|
"Cannot make file object of OpenSSL.SSL.Connection")
|
|
|
|
def get_app_data(self):
|
|
"""
|
|
Retrieve application data as set by :meth:`set_app_data`.
|
|
|
|
:return: The application data
|
|
"""
|
|
return self._app_data
|
|
|
|
def set_app_data(self, data):
|
|
"""
|
|
Set application data
|
|
|
|
:param data: The application data
|
|
:return: None
|
|
"""
|
|
self._app_data = data
|
|
|
|
def get_shutdown(self):
|
|
"""
|
|
Get the shutdown state of the Connection.
|
|
|
|
:return: The shutdown state, a bitvector of SENT_SHUTDOWN,
|
|
RECEIVED_SHUTDOWN.
|
|
"""
|
|
return _lib.SSL_get_shutdown(self._ssl)
|
|
|
|
def set_shutdown(self, state):
|
|
"""
|
|
Set the shutdown state of the Connection.
|
|
|
|
:param state: bitvector of SENT_SHUTDOWN, RECEIVED_SHUTDOWN.
|
|
:return: None
|
|
"""
|
|
if not isinstance(state, integer_types):
|
|
raise TypeError("state must be an integer")
|
|
|
|
_lib.SSL_set_shutdown(self._ssl, state)
|
|
|
|
def get_state_string(self):
|
|
"""
|
|
Retrieve a verbose string detailing the state of the Connection.
|
|
|
|
:return: A string representing the state
|
|
:rtype: bytes
|
|
"""
|
|
return _ffi.string(_lib.SSL_state_string_long(self._ssl))
|
|
|
|
def server_random(self):
|
|
"""
|
|
Retrieve the random value used with the server hello message.
|
|
|
|
:return: A string representing the state
|
|
"""
|
|
session = _lib.SSL_get_session(self._ssl)
|
|
if session == _ffi.NULL:
|
|
return None
|
|
length = _lib.SSL_get_server_random(self._ssl, _ffi.NULL, 0)
|
|
assert length > 0
|
|
outp = _no_zero_allocator("unsigned char[]", length)
|
|
_lib.SSL_get_server_random(self._ssl, outp, length)
|
|
return _ffi.buffer(outp, length)[:]
|
|
|
|
def client_random(self):
|
|
"""
|
|
Retrieve the random value used with the client hello message.
|
|
|
|
:return: A string representing the state
|
|
"""
|
|
session = _lib.SSL_get_session(self._ssl)
|
|
if session == _ffi.NULL:
|
|
return None
|
|
|
|
length = _lib.SSL_get_client_random(self._ssl, _ffi.NULL, 0)
|
|
assert length > 0
|
|
outp = _no_zero_allocator("unsigned char[]", length)
|
|
_lib.SSL_get_client_random(self._ssl, outp, length)
|
|
return _ffi.buffer(outp, length)[:]
|
|
|
|
def master_key(self):
|
|
"""
|
|
Retrieve the value of the master key for this session.
|
|
|
|
:return: A string representing the state
|
|
"""
|
|
session = _lib.SSL_get_session(self._ssl)
|
|
if session == _ffi.NULL:
|
|
return None
|
|
|
|
length = _lib.SSL_SESSION_get_master_key(session, _ffi.NULL, 0)
|
|
assert length > 0
|
|
outp = _no_zero_allocator("unsigned char[]", length)
|
|
_lib.SSL_SESSION_get_master_key(session, outp, length)
|
|
return _ffi.buffer(outp, length)[:]
|
|
|
|
def export_keying_material(self, label, olen, context=None):
|
|
"""
|
|
Obtain keying material for application use.
|
|
|
|
:param: label - a disambiguating label string as described in RFC 5705
|
|
:param: olen - the length of the exported key material in bytes
|
|
:param: context - a per-association context value
|
|
:return: the exported key material bytes or None
|
|
"""
|
|
outp = _no_zero_allocator("unsigned char[]", olen)
|
|
context_buf = _ffi.NULL
|
|
context_len = 0
|
|
use_context = 0
|
|
if context is not None:
|
|
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)
|
|
_openssl_assert(success == 1)
|
|
return _ffi.buffer(outp, olen)[:]
|
|
|
|
def sock_shutdown(self, *args, **kwargs):
|
|
"""
|
|
Call the :meth:`shutdown` method of the underlying socket.
|
|
See :manpage:`shutdown(2)`.
|
|
|
|
:return: What the socket's shutdown() method returns
|
|
"""
|
|
return self._socket.shutdown(*args, **kwargs)
|
|
|
|
def get_certificate(self):
|
|
"""
|
|
Retrieve the local certificate (if any)
|
|
|
|
:return: The local certificate
|
|
"""
|
|
cert = _lib.SSL_get_certificate(self._ssl)
|
|
if cert != _ffi.NULL:
|
|
_lib.X509_up_ref(cert)
|
|
return X509._from_raw_x509_ptr(cert)
|
|
return None
|
|
|
|
def get_peer_certificate(self):
|
|
"""
|
|
Retrieve the other side's certificate (if any)
|
|
|
|
:return: The peer's certificate
|
|
"""
|
|
cert = _lib.SSL_get_peer_certificate(self._ssl)
|
|
if cert != _ffi.NULL:
|
|
return X509._from_raw_x509_ptr(cert)
|
|
return None
|
|
|
|
def get_peer_cert_chain(self):
|
|
"""
|
|
Retrieve the other side's certificate (if any)
|
|
|
|
:return: A list of X509 instances giving the peer's certificate chain,
|
|
or None if it does not have one.
|
|
"""
|
|
cert_stack = _lib.SSL_get_peer_cert_chain(self._ssl)
|
|
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
|
|
|
|
def want_read(self):
|
|
"""
|
|
Checks if more data has to be read from the transport layer to complete
|
|
an operation.
|
|
|
|
:return: True iff more data has to be read
|
|
"""
|
|
return _lib.SSL_want_read(self._ssl)
|
|
|
|
def want_write(self):
|
|
"""
|
|
Checks if there is data to write to the transport layer to complete an
|
|
operation.
|
|
|
|
:return: True iff there is data to write
|
|
"""
|
|
return _lib.SSL_want_write(self._ssl)
|
|
|
|
def set_accept_state(self):
|
|
"""
|
|
Set the connection to work in server mode. The handshake will be
|
|
handled automatically by read/write.
|
|
|
|
:return: None
|
|
"""
|
|
_lib.SSL_set_accept_state(self._ssl)
|
|
|
|
def set_connect_state(self):
|
|
"""
|
|
Set the connection to work in client mode. The handshake will be
|
|
handled automatically by read/write.
|
|
|
|
:return: None
|
|
"""
|
|
_lib.SSL_set_connect_state(self._ssl)
|
|
|
|
def get_session(self):
|
|
"""
|
|
Returns the Session currently used.
|
|
|
|
:return: An instance of :class:`OpenSSL.SSL.Session` or
|
|
:obj:`None` if no session exists.
|
|
|
|
.. versionadded:: 0.14
|
|
"""
|
|
session = _lib.SSL_get1_session(self._ssl)
|
|
if session == _ffi.NULL:
|
|
return None
|
|
|
|
pysession = Session.__new__(Session)
|
|
pysession._session = _ffi.gc(session, _lib.SSL_SESSION_free)
|
|
return pysession
|
|
|
|
def set_session(self, session):
|
|
"""
|
|
Set the session to be used when the TLS/SSL connection is established.
|
|
|
|
:param session: A Session instance representing the session to use.
|
|
:returns: None
|
|
|
|
.. versionadded:: 0.14
|
|
"""
|
|
if not isinstance(session, Session):
|
|
raise TypeError("session must be a Session instance")
|
|
|
|
result = _lib.SSL_set_session(self._ssl, session._session)
|
|
if not result:
|
|
_raise_current_error()
|
|
|
|
def _get_finished_message(self, function):
|
|
"""
|
|
Helper to implement :meth:`get_finished` and
|
|
:meth:`get_peer_finished`.
|
|
|
|
:param function: Either :data:`SSL_get_finished`: or
|
|
:data:`SSL_get_peer_finished`.
|
|
|
|
:return: :data:`None` if the desired message has not yet been
|
|
received, otherwise the contents of the message.
|
|
:rtype: :class:`bytes` or :class:`NoneType`
|
|
"""
|
|
# The OpenSSL documentation says nothing about what might happen if the
|
|
# count argument given is zero. Specifically, it doesn't say whether
|
|
# the output buffer may be NULL in that case or not. Inspection of the
|
|
# implementation reveals that it calls memcpy() unconditionally.
|
|
# Section 7.1.4, paragraph 1 of the C standard suggests that
|
|
# memcpy(NULL, source, 0) is not guaranteed to produce defined (let
|
|
# alone desirable) behavior (though it probably does on just about
|
|
# every implementation...)
|
|
#
|
|
# Allocate a tiny buffer to pass in (instead of just passing NULL as
|
|
# one might expect) for the initial call so as to be safe against this
|
|
# potentially undefined behavior.
|
|
empty = _ffi.new("char[]", 0)
|
|
size = function(self._ssl, empty, 0)
|
|
if size == 0:
|
|
# No Finished message so far.
|
|
return None
|
|
|
|
buf = _no_zero_allocator("char[]", size)
|
|
function(self._ssl, buf, size)
|
|
return _ffi.buffer(buf, size)[:]
|
|
|
|
def get_finished(self):
|
|
"""
|
|
Obtain the latest TLS Finished message that we sent.
|
|
|
|
:return: The contents of the message or :obj:`None` if the TLS
|
|
handshake has not yet completed.
|
|
:rtype: :class:`bytes` or :class:`NoneType`
|
|
|
|
.. versionadded:: 0.15
|
|
"""
|
|
return self._get_finished_message(_lib.SSL_get_finished)
|
|
|
|
def get_peer_finished(self):
|
|
"""
|
|
Obtain the latest TLS Finished message that we received from the peer.
|
|
|
|
:return: The contents of the message or :obj:`None` if the TLS
|
|
handshake has not yet completed.
|
|
:rtype: :class:`bytes` or :class:`NoneType`
|
|
|
|
.. versionadded:: 0.15
|
|
"""
|
|
return self._get_finished_message(_lib.SSL_get_peer_finished)
|
|
|
|
def get_cipher_name(self):
|
|
"""
|
|
Obtain the name of the currently used cipher.
|
|
|
|
:returns: The name of the currently used cipher or :obj:`None`
|
|
if no connection has been established.
|
|
:rtype: :class:`unicode` or :class:`NoneType`
|
|
|
|
.. versionadded:: 0.15
|
|
"""
|
|
cipher = _lib.SSL_get_current_cipher(self._ssl)
|
|
if cipher == _ffi.NULL:
|
|
return None
|
|
else:
|
|
name = _ffi.string(_lib.SSL_CIPHER_get_name(cipher))
|
|
return name.decode("utf-8")
|
|
|
|
def get_cipher_bits(self):
|
|
"""
|
|
Obtain the number of secret bits of the currently used cipher.
|
|
|
|
:returns: The number of secret bits of the currently used cipher
|
|
or :obj:`None` if no connection has been established.
|
|
:rtype: :class:`int` or :class:`NoneType`
|
|
|
|
.. versionadded:: 0.15
|
|
"""
|
|
cipher = _lib.SSL_get_current_cipher(self._ssl)
|
|
if cipher == _ffi.NULL:
|
|
return None
|
|
else:
|
|
return _lib.SSL_CIPHER_get_bits(cipher, _ffi.NULL)
|
|
|
|
def get_cipher_version(self):
|
|
"""
|
|
Obtain the protocol version of the currently used cipher.
|
|
|
|
:returns: The protocol name of the currently used cipher
|
|
or :obj:`None` if no connection has been established.
|
|
:rtype: :class:`unicode` or :class:`NoneType`
|
|
|
|
.. versionadded:: 0.15
|
|
"""
|
|
cipher = _lib.SSL_get_current_cipher(self._ssl)
|
|
if cipher == _ffi.NULL:
|
|
return None
|
|
else:
|
|
version = _ffi.string(_lib.SSL_CIPHER_get_version(cipher))
|
|
return version.decode("utf-8")
|
|
|
|
def get_protocol_version_name(self):
|
|
"""
|
|
Retrieve the protocol version of the current connection.
|
|
|
|
:returns: The TLS version of the current connection, for example
|
|
the value for TLS 1.2 would be ``TLSv1.2``or ``Unknown``
|
|
for connections that were not successfully established.
|
|
:rtype: :class:`unicode`
|
|
"""
|
|
version = _ffi.string(_lib.SSL_get_version(self._ssl))
|
|
return version.decode("utf-8")
|
|
|
|
def get_protocol_version(self):
|
|
"""
|
|
Retrieve the SSL or TLS protocol version of the current connection.
|
|
|
|
:returns: The TLS version of the current connection. For example,
|
|
it will return ``0x769`` for connections made over TLS version 1.
|
|
:rtype: :class:`int`
|
|
"""
|
|
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):
|
|
"""
|
|
Specify the client's ALPN protocol list.
|
|
|
|
These protocols are offered to the server during protocol negotiation.
|
|
|
|
:param protos: A list of the protocols to be offered to the server.
|
|
This list should be a Python list of bytestrings representing the
|
|
protocols to offer, e.g. ``[b'http/1.1', b'spdy/2']``.
|
|
"""
|
|
# Take the list of protocols and join them together, prefixing them
|
|
# with their lengths.
|
|
protostr = b''.join(
|
|
chain.from_iterable((int2byte(len(p)), p) for p in protos)
|
|
)
|
|
|
|
# Build a C string from the list. We don't need to save this off
|
|
# because OpenSSL immediately copies the data out.
|
|
input_str = _ffi.new("unsigned char[]", protostr)
|
|
_lib.SSL_set_alpn_protos(self._ssl, input_str, len(protostr))
|
|
|
|
@_requires_alpn
|
|
def get_alpn_proto_negotiated(self):
|
|
"""
|
|
Get the protocol that was negotiated by ALPN.
|
|
|
|
:returns: A bytestring of the protocol name. If no protocol has been
|
|
negotiated yet, returns an empty string.
|
|
"""
|
|
data = _ffi.new("unsigned char **")
|
|
data_len = _ffi.new("unsigned int *")
|
|
|
|
_lib.SSL_get0_alpn_selected(self._ssl, data, data_len)
|
|
|
|
if not data_len:
|
|
return b''
|
|
|
|
return _ffi.buffer(data[0], data_len[0])[:]
|
|
|
|
def request_ocsp(self):
|
|
"""
|
|
Called to request that the server sends stapled OCSP data, if
|
|
available. If this is not called on the client side then the server
|
|
will not send OCSP data. Should be used in conjunction with
|
|
:meth:`Context.set_ocsp_client_callback`.
|
|
"""
|
|
rc = _lib.SSL_set_tlsext_status_type(
|
|
self._ssl, _lib.TLSEXT_STATUSTYPE_ocsp
|
|
)
|
|
_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()
|