Issue #19500: Add client-side SSL session resumption to the ssl module.
This commit is contained in:
parent
d04863771b
commit
99a6570295
|
@ -776,6 +776,10 @@ Constants
|
||||||
|
|
||||||
:class:`enum.IntFlag` collection of OP_* constants.
|
:class:`enum.IntFlag` collection of OP_* constants.
|
||||||
|
|
||||||
|
.. data:: OP_NO_TICKET
|
||||||
|
|
||||||
|
Prevent client side from requesting a session ticket.
|
||||||
|
|
||||||
.. versionadded:: 3.6
|
.. versionadded:: 3.6
|
||||||
|
|
||||||
.. data:: HAS_ALPN
|
.. data:: HAS_ALPN
|
||||||
|
@ -1176,6 +1180,19 @@ SSL sockets also have the following additional methods and attributes:
|
||||||
|
|
||||||
.. versionadded:: 3.2
|
.. versionadded:: 3.2
|
||||||
|
|
||||||
|
.. attribute:: SSLSocket.session
|
||||||
|
|
||||||
|
The :class:`SSLSession` for this SSL connection. The session is available
|
||||||
|
for client and server side sockets after the TLS handshake has been
|
||||||
|
performed. For client sockets the session can be set before
|
||||||
|
:meth:`~SSLSocket.do_handshake` has been called to reuse a session.
|
||||||
|
|
||||||
|
.. versionadded:: 3.6
|
||||||
|
|
||||||
|
.. attribute:: SSLSocket.session_reused
|
||||||
|
|
||||||
|
.. versionadded:: 3.6
|
||||||
|
|
||||||
|
|
||||||
SSL Contexts
|
SSL Contexts
|
||||||
------------
|
------------
|
||||||
|
@ -1509,7 +1526,7 @@ to speed up repeated connections from the same clients.
|
||||||
|
|
||||||
.. method:: SSLContext.wrap_socket(sock, server_side=False, \
|
.. method:: SSLContext.wrap_socket(sock, server_side=False, \
|
||||||
do_handshake_on_connect=True, suppress_ragged_eofs=True, \
|
do_handshake_on_connect=True, suppress_ragged_eofs=True, \
|
||||||
server_hostname=None)
|
server_hostname=None, session=None)
|
||||||
|
|
||||||
Wrap an existing Python socket *sock* and return an :class:`SSLSocket`
|
Wrap an existing Python socket *sock* and return an :class:`SSLSocket`
|
||||||
object. *sock* must be a :data:`~socket.SOCK_STREAM` socket; other socket
|
object. *sock* must be a :data:`~socket.SOCK_STREAM` socket; other socket
|
||||||
|
@ -1526,19 +1543,27 @@ to speed up repeated connections from the same clients.
|
||||||
quite similarly to HTTP virtual hosts. Specifying *server_hostname* will
|
quite similarly to HTTP virtual hosts. Specifying *server_hostname* will
|
||||||
raise a :exc:`ValueError` if *server_side* is true.
|
raise a :exc:`ValueError` if *server_side* is true.
|
||||||
|
|
||||||
|
*session*, see :attr:`~SSLSocket.session`.
|
||||||
|
|
||||||
.. versionchanged:: 3.5
|
.. versionchanged:: 3.5
|
||||||
Always allow a server_hostname to be passed, even if OpenSSL does not
|
Always allow a server_hostname to be passed, even if OpenSSL does not
|
||||||
have SNI.
|
have SNI.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.6
|
||||||
|
*session* argument was added.
|
||||||
|
|
||||||
.. method:: SSLContext.wrap_bio(incoming, outgoing, server_side=False, \
|
.. method:: SSLContext.wrap_bio(incoming, outgoing, server_side=False, \
|
||||||
server_hostname=None)
|
server_hostname=None, session=None)
|
||||||
|
|
||||||
Create a new :class:`SSLObject` instance by wrapping the BIO objects
|
Create a new :class:`SSLObject` instance by wrapping the BIO objects
|
||||||
*incoming* and *outgoing*. The SSL routines will read input data from the
|
*incoming* and *outgoing*. The SSL routines will read input data from the
|
||||||
incoming BIO and write data to the outgoing BIO.
|
incoming BIO and write data to the outgoing BIO.
|
||||||
|
|
||||||
The *server_side* and *server_hostname* parameters have the same meaning as
|
The *server_side*, *server_hostname* and *session* parameters have the
|
||||||
in :meth:`SSLContext.wrap_socket`.
|
same meaning as in :meth:`SSLContext.wrap_socket`.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.6
|
||||||
|
*session* argument was added.
|
||||||
|
|
||||||
.. method:: SSLContext.session_stats()
|
.. method:: SSLContext.session_stats()
|
||||||
|
|
||||||
|
@ -2045,6 +2070,8 @@ provided.
|
||||||
- :attr:`~SSLSocket.context`
|
- :attr:`~SSLSocket.context`
|
||||||
- :attr:`~SSLSocket.server_side`
|
- :attr:`~SSLSocket.server_side`
|
||||||
- :attr:`~SSLSocket.server_hostname`
|
- :attr:`~SSLSocket.server_hostname`
|
||||||
|
- :attr:`~SSLSocket.session`
|
||||||
|
- :attr:`~SSLSocket.session_reused`
|
||||||
- :meth:`~SSLSocket.read`
|
- :meth:`~SSLSocket.read`
|
||||||
- :meth:`~SSLSocket.write`
|
- :meth:`~SSLSocket.write`
|
||||||
- :meth:`~SSLSocket.getpeercert`
|
- :meth:`~SSLSocket.getpeercert`
|
||||||
|
@ -2126,6 +2153,22 @@ purpose. It wraps an OpenSSL memory BIO (Basic IO) object:
|
||||||
become true after all data currently in the buffer has been read.
|
become true after all data currently in the buffer has been read.
|
||||||
|
|
||||||
|
|
||||||
|
SSL session
|
||||||
|
-----------
|
||||||
|
|
||||||
|
.. versionadded:: 3.6
|
||||||
|
|
||||||
|
.. class:: SSLSession
|
||||||
|
|
||||||
|
Session object used by :attr:`~SSLSocket.session`.
|
||||||
|
|
||||||
|
.. attribute:: id
|
||||||
|
.. attribute:: time
|
||||||
|
.. attribute:: timeout
|
||||||
|
.. attribute:: ticket_lifetime_hint
|
||||||
|
.. attribute:: has_ticket
|
||||||
|
|
||||||
|
|
||||||
.. _ssl-security:
|
.. _ssl-security:
|
||||||
|
|
||||||
Security considerations
|
Security considerations
|
||||||
|
|
65
Lib/ssl.py
65
Lib/ssl.py
|
@ -99,7 +99,7 @@ from enum import Enum as _Enum, IntEnum as _IntEnum, IntFlag as _IntFlag
|
||||||
import _ssl # if we can't import it, let the error propagate
|
import _ssl # if we can't import it, let the error propagate
|
||||||
|
|
||||||
from _ssl import OPENSSL_VERSION_NUMBER, OPENSSL_VERSION_INFO, OPENSSL_VERSION
|
from _ssl import OPENSSL_VERSION_NUMBER, OPENSSL_VERSION_INFO, OPENSSL_VERSION
|
||||||
from _ssl import _SSLContext, MemoryBIO
|
from _ssl import _SSLContext, MemoryBIO, SSLSession
|
||||||
from _ssl import (
|
from _ssl import (
|
||||||
SSLError, SSLZeroReturnError, SSLWantReadError, SSLWantWriteError,
|
SSLError, SSLZeroReturnError, SSLWantReadError, SSLWantWriteError,
|
||||||
SSLSyscallError, SSLEOFError,
|
SSLSyscallError, SSLEOFError,
|
||||||
|
@ -391,18 +391,18 @@ class SSLContext(_SSLContext):
|
||||||
def wrap_socket(self, sock, server_side=False,
|
def wrap_socket(self, sock, server_side=False,
|
||||||
do_handshake_on_connect=True,
|
do_handshake_on_connect=True,
|
||||||
suppress_ragged_eofs=True,
|
suppress_ragged_eofs=True,
|
||||||
server_hostname=None):
|
server_hostname=None, session=None):
|
||||||
return SSLSocket(sock=sock, server_side=server_side,
|
return SSLSocket(sock=sock, server_side=server_side,
|
||||||
do_handshake_on_connect=do_handshake_on_connect,
|
do_handshake_on_connect=do_handshake_on_connect,
|
||||||
suppress_ragged_eofs=suppress_ragged_eofs,
|
suppress_ragged_eofs=suppress_ragged_eofs,
|
||||||
server_hostname=server_hostname,
|
server_hostname=server_hostname,
|
||||||
_context=self)
|
_context=self, _session=session)
|
||||||
|
|
||||||
def wrap_bio(self, incoming, outgoing, server_side=False,
|
def wrap_bio(self, incoming, outgoing, server_side=False,
|
||||||
server_hostname=None):
|
server_hostname=None, session=None):
|
||||||
sslobj = self._wrap_bio(incoming, outgoing, server_side=server_side,
|
sslobj = self._wrap_bio(incoming, outgoing, server_side=server_side,
|
||||||
server_hostname=server_hostname)
|
server_hostname=server_hostname)
|
||||||
return SSLObject(sslobj)
|
return SSLObject(sslobj, session=session)
|
||||||
|
|
||||||
def set_npn_protocols(self, npn_protocols):
|
def set_npn_protocols(self, npn_protocols):
|
||||||
protos = bytearray()
|
protos = bytearray()
|
||||||
|
@ -572,10 +572,12 @@ class SSLObject:
|
||||||
* The ``do_handshake_on_connect`` and ``suppress_ragged_eofs`` machinery.
|
* The ``do_handshake_on_connect`` and ``suppress_ragged_eofs`` machinery.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, sslobj, owner=None):
|
def __init__(self, sslobj, owner=None, session=None):
|
||||||
self._sslobj = sslobj
|
self._sslobj = sslobj
|
||||||
# Note: _sslobj takes a weak reference to owner
|
# Note: _sslobj takes a weak reference to owner
|
||||||
self._sslobj.owner = owner or self
|
self._sslobj.owner = owner or self
|
||||||
|
if session is not None:
|
||||||
|
self._sslobj.session = session
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def context(self):
|
def context(self):
|
||||||
|
@ -586,6 +588,20 @@ class SSLObject:
|
||||||
def context(self, ctx):
|
def context(self, ctx):
|
||||||
self._sslobj.context = ctx
|
self._sslobj.context = ctx
|
||||||
|
|
||||||
|
@property
|
||||||
|
def session(self):
|
||||||
|
"""The SSLSession for client socket."""
|
||||||
|
return self._sslobj.session
|
||||||
|
|
||||||
|
@session.setter
|
||||||
|
def session(self, session):
|
||||||
|
self._sslobj.session = session
|
||||||
|
|
||||||
|
@property
|
||||||
|
def session_reused(self):
|
||||||
|
"""Was the client session reused during handshake"""
|
||||||
|
return self._sslobj.session_reused
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def server_side(self):
|
def server_side(self):
|
||||||
"""Whether this is a server-side socket."""
|
"""Whether this is a server-side socket."""
|
||||||
|
@ -703,7 +719,7 @@ class SSLSocket(socket):
|
||||||
family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None,
|
family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None,
|
||||||
suppress_ragged_eofs=True, npn_protocols=None, ciphers=None,
|
suppress_ragged_eofs=True, npn_protocols=None, ciphers=None,
|
||||||
server_hostname=None,
|
server_hostname=None,
|
||||||
_context=None):
|
_context=None, _session=None):
|
||||||
|
|
||||||
if _context:
|
if _context:
|
||||||
self._context = _context
|
self._context = _context
|
||||||
|
@ -735,11 +751,16 @@ class SSLSocket(socket):
|
||||||
# mixed in.
|
# mixed in.
|
||||||
if sock.getsockopt(SOL_SOCKET, SO_TYPE) != SOCK_STREAM:
|
if sock.getsockopt(SOL_SOCKET, SO_TYPE) != SOCK_STREAM:
|
||||||
raise NotImplementedError("only stream sockets are supported")
|
raise NotImplementedError("only stream sockets are supported")
|
||||||
if server_side and server_hostname:
|
if server_side:
|
||||||
raise ValueError("server_hostname can only be specified "
|
if server_hostname:
|
||||||
"in client mode")
|
raise ValueError("server_hostname can only be specified "
|
||||||
|
"in client mode")
|
||||||
|
if _session is not None:
|
||||||
|
raise ValueError("session can only be specified in "
|
||||||
|
"client mode")
|
||||||
if self._context.check_hostname and not server_hostname:
|
if self._context.check_hostname and not server_hostname:
|
||||||
raise ValueError("check_hostname requires server_hostname")
|
raise ValueError("check_hostname requires server_hostname")
|
||||||
|
self._session = _session
|
||||||
self.server_side = server_side
|
self.server_side = server_side
|
||||||
self.server_hostname = server_hostname
|
self.server_hostname = server_hostname
|
||||||
self.do_handshake_on_connect = do_handshake_on_connect
|
self.do_handshake_on_connect = do_handshake_on_connect
|
||||||
|
@ -775,7 +796,8 @@ class SSLSocket(socket):
|
||||||
try:
|
try:
|
||||||
sslobj = self._context._wrap_socket(self, server_side,
|
sslobj = self._context._wrap_socket(self, server_side,
|
||||||
server_hostname)
|
server_hostname)
|
||||||
self._sslobj = SSLObject(sslobj, owner=self)
|
self._sslobj = SSLObject(sslobj, owner=self,
|
||||||
|
session=self._session)
|
||||||
if do_handshake_on_connect:
|
if do_handshake_on_connect:
|
||||||
timeout = self.gettimeout()
|
timeout = self.gettimeout()
|
||||||
if timeout == 0.0:
|
if timeout == 0.0:
|
||||||
|
@ -796,6 +818,24 @@ class SSLSocket(socket):
|
||||||
self._context = ctx
|
self._context = ctx
|
||||||
self._sslobj.context = ctx
|
self._sslobj.context = ctx
|
||||||
|
|
||||||
|
@property
|
||||||
|
def session(self):
|
||||||
|
"""The SSLSession for client socket."""
|
||||||
|
if self._sslobj is not None:
|
||||||
|
return self._sslobj.session
|
||||||
|
|
||||||
|
@session.setter
|
||||||
|
def session(self, session):
|
||||||
|
self._session = session
|
||||||
|
if self._sslobj is not None:
|
||||||
|
self._sslobj.session = session
|
||||||
|
|
||||||
|
@property
|
||||||
|
def session_reused(self):
|
||||||
|
"""Was the client session reused during handshake"""
|
||||||
|
if self._sslobj is not None:
|
||||||
|
return self._sslobj.session_reused
|
||||||
|
|
||||||
def dup(self):
|
def dup(self):
|
||||||
raise NotImplemented("Can't dup() %s instances" %
|
raise NotImplemented("Can't dup() %s instances" %
|
||||||
self.__class__.__name__)
|
self.__class__.__name__)
|
||||||
|
@ -1028,7 +1068,8 @@ class SSLSocket(socket):
|
||||||
if self._connected:
|
if self._connected:
|
||||||
raise ValueError("attempt to connect already-connected SSLSocket!")
|
raise ValueError("attempt to connect already-connected SSLSocket!")
|
||||||
sslobj = self.context._wrap_socket(self, False, self.server_hostname)
|
sslobj = self.context._wrap_socket(self, False, self.server_hostname)
|
||||||
self._sslobj = SSLObject(sslobj, owner=self)
|
self._sslobj = SSLObject(sslobj, owner=self,
|
||||||
|
session=self._session)
|
||||||
try:
|
try:
|
||||||
if connect_ex:
|
if connect_ex:
|
||||||
rc = socket.connect_ex(self, addr)
|
rc = socket.connect_ex(self, addr)
|
||||||
|
|
|
@ -2163,7 +2163,8 @@ if _have_threads:
|
||||||
self.server.close()
|
self.server.close()
|
||||||
|
|
||||||
def server_params_test(client_context, server_context, indata=b"FOO\n",
|
def server_params_test(client_context, server_context, indata=b"FOO\n",
|
||||||
chatty=True, connectionchatty=False, sni_name=None):
|
chatty=True, connectionchatty=False, sni_name=None,
|
||||||
|
session=None):
|
||||||
"""
|
"""
|
||||||
Launch a server, connect a client to it and try various reads
|
Launch a server, connect a client to it and try various reads
|
||||||
and writes.
|
and writes.
|
||||||
|
@ -2174,7 +2175,7 @@ if _have_threads:
|
||||||
connectionchatty=False)
|
connectionchatty=False)
|
||||||
with server:
|
with server:
|
||||||
with client_context.wrap_socket(socket.socket(),
|
with client_context.wrap_socket(socket.socket(),
|
||||||
server_hostname=sni_name) as s:
|
server_hostname=sni_name, session=session) as s:
|
||||||
s.connect((HOST, server.port))
|
s.connect((HOST, server.port))
|
||||||
for arg in [indata, bytearray(indata), memoryview(indata)]:
|
for arg in [indata, bytearray(indata), memoryview(indata)]:
|
||||||
if connectionchatty:
|
if connectionchatty:
|
||||||
|
@ -2202,6 +2203,8 @@ if _have_threads:
|
||||||
'client_alpn_protocol': s.selected_alpn_protocol(),
|
'client_alpn_protocol': s.selected_alpn_protocol(),
|
||||||
'client_npn_protocol': s.selected_npn_protocol(),
|
'client_npn_protocol': s.selected_npn_protocol(),
|
||||||
'version': s.version(),
|
'version': s.version(),
|
||||||
|
'session_reused': s.session_reused,
|
||||||
|
'session': s.session,
|
||||||
})
|
})
|
||||||
s.close()
|
s.close()
|
||||||
stats['server_alpn_protocols'] = server.selected_alpn_protocols
|
stats['server_alpn_protocols'] = server.selected_alpn_protocols
|
||||||
|
@ -3412,6 +3415,111 @@ if _have_threads:
|
||||||
s.sendfile(file)
|
s.sendfile(file)
|
||||||
self.assertEqual(s.recv(1024), TEST_DATA)
|
self.assertEqual(s.recv(1024), TEST_DATA)
|
||||||
|
|
||||||
|
def test_session(self):
|
||||||
|
server_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
|
||||||
|
server_context.load_cert_chain(SIGNED_CERTFILE)
|
||||||
|
client_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
|
||||||
|
client_context.verify_mode = ssl.CERT_REQUIRED
|
||||||
|
client_context.load_verify_locations(SIGNING_CA)
|
||||||
|
|
||||||
|
# first conncetion without session
|
||||||
|
stats = server_params_test(client_context, server_context)
|
||||||
|
session = stats['session']
|
||||||
|
self.assertTrue(session.id)
|
||||||
|
self.assertGreater(session.time, 0)
|
||||||
|
self.assertGreater(session.timeout, 0)
|
||||||
|
self.assertTrue(session.has_ticket)
|
||||||
|
if ssl.OPENSSL_VERSION_INFO > (1, 0, 1):
|
||||||
|
self.assertGreater(session.ticket_lifetime_hint, 0)
|
||||||
|
self.assertFalse(stats['session_reused'])
|
||||||
|
sess_stat = server_context.session_stats()
|
||||||
|
self.assertEqual(sess_stat['accept'], 1)
|
||||||
|
self.assertEqual(sess_stat['hits'], 0)
|
||||||
|
|
||||||
|
# reuse session
|
||||||
|
stats = server_params_test(client_context, server_context, session=session)
|
||||||
|
sess_stat = server_context.session_stats()
|
||||||
|
self.assertEqual(sess_stat['accept'], 2)
|
||||||
|
self.assertEqual(sess_stat['hits'], 1)
|
||||||
|
self.assertTrue(stats['session_reused'])
|
||||||
|
session2 = stats['session']
|
||||||
|
self.assertEqual(session2.id, session.id)
|
||||||
|
self.assertEqual(session2, session)
|
||||||
|
self.assertIsNot(session2, session)
|
||||||
|
self.assertGreaterEqual(session2.time, session.time)
|
||||||
|
self.assertGreaterEqual(session2.timeout, session.timeout)
|
||||||
|
|
||||||
|
# another one without session
|
||||||
|
stats = server_params_test(client_context, server_context)
|
||||||
|
self.assertFalse(stats['session_reused'])
|
||||||
|
session3 = stats['session']
|
||||||
|
self.assertNotEqual(session3.id, session.id)
|
||||||
|
self.assertNotEqual(session3, session)
|
||||||
|
sess_stat = server_context.session_stats()
|
||||||
|
self.assertEqual(sess_stat['accept'], 3)
|
||||||
|
self.assertEqual(sess_stat['hits'], 1)
|
||||||
|
|
||||||
|
# reuse session again
|
||||||
|
stats = server_params_test(client_context, server_context, session=session)
|
||||||
|
self.assertTrue(stats['session_reused'])
|
||||||
|
session4 = stats['session']
|
||||||
|
self.assertEqual(session4.id, session.id)
|
||||||
|
self.assertEqual(session4, session)
|
||||||
|
self.assertGreaterEqual(session4.time, session.time)
|
||||||
|
self.assertGreaterEqual(session4.timeout, session.timeout)
|
||||||
|
sess_stat = server_context.session_stats()
|
||||||
|
self.assertEqual(sess_stat['accept'], 4)
|
||||||
|
self.assertEqual(sess_stat['hits'], 2)
|
||||||
|
|
||||||
|
def test_session_handling(self):
|
||||||
|
context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
|
||||||
|
context.verify_mode = ssl.CERT_REQUIRED
|
||||||
|
context.load_verify_locations(CERTFILE)
|
||||||
|
context.load_cert_chain(CERTFILE)
|
||||||
|
|
||||||
|
context2 = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
|
||||||
|
context2.verify_mode = ssl.CERT_REQUIRED
|
||||||
|
context2.load_verify_locations(CERTFILE)
|
||||||
|
context2.load_cert_chain(CERTFILE)
|
||||||
|
|
||||||
|
server = ThreadedEchoServer(context=context, chatty=False)
|
||||||
|
with server:
|
||||||
|
with context.wrap_socket(socket.socket()) as s:
|
||||||
|
# session is None before handshake
|
||||||
|
self.assertEqual(s.session, None)
|
||||||
|
self.assertEqual(s.session_reused, None)
|
||||||
|
s.connect((HOST, server.port))
|
||||||
|
session = s.session
|
||||||
|
self.assertTrue(session)
|
||||||
|
with self.assertRaises(TypeError) as e:
|
||||||
|
s.session = object
|
||||||
|
self.assertEqual(str(e.exception), 'Value is not a SSLSession.')
|
||||||
|
|
||||||
|
with context.wrap_socket(socket.socket()) as s:
|
||||||
|
s.connect((HOST, server.port))
|
||||||
|
# cannot set session after handshake
|
||||||
|
with self.assertRaises(ValueError) as e:
|
||||||
|
s.session = session
|
||||||
|
self.assertEqual(str(e.exception),
|
||||||
|
'Cannot set session after handshake.')
|
||||||
|
|
||||||
|
with context.wrap_socket(socket.socket()) as s:
|
||||||
|
# can set session before handshake and before the
|
||||||
|
# connection was established
|
||||||
|
s.session = session
|
||||||
|
s.connect((HOST, server.port))
|
||||||
|
self.assertEqual(s.session.id, session.id)
|
||||||
|
self.assertEqual(s.session, session)
|
||||||
|
self.assertEqual(s.session_reused, True)
|
||||||
|
|
||||||
|
with context2.wrap_socket(socket.socket()) as s:
|
||||||
|
# cannot re-use session with a different SSLContext
|
||||||
|
with self.assertRaises(ValueError) as e:
|
||||||
|
s.session = session
|
||||||
|
s.connect((HOST, server.port))
|
||||||
|
self.assertEqual(str(e.exception),
|
||||||
|
'Session refers to a different SSLContext.')
|
||||||
|
|
||||||
|
|
||||||
def test_main(verbose=False):
|
def test_main(verbose=False):
|
||||||
if support.verbose:
|
if support.verbose:
|
||||||
|
|
|
@ -138,6 +138,8 @@ Core and Builtins
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Issue #19500: Add client-side SSL session resumption to the ssl module.
|
||||||
|
|
||||||
- Issue #28022: Deprecate ssl-related arguments in favor of SSLContext. The
|
- Issue #28022: Deprecate ssl-related arguments in favor of SSLContext. The
|
||||||
deprecation include manual creation of SSLSocket and certfile/keyfile
|
deprecation include manual creation of SSLSocket and certfile/keyfile
|
||||||
(or similar) in ftplib, httplib, imaplib, smtplib, poplib and urllib.
|
(or similar) in ftplib, httplib, imaplib, smtplib, poplib and urllib.
|
||||||
|
|
372
Modules/_ssl.c
372
Modules/_ssl.c
|
@ -187,6 +187,19 @@ static X509_VERIFY_PARAM *X509_STORE_get0_param(X509_STORE *store)
|
||||||
{
|
{
|
||||||
return store->param;
|
return store->param;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
SSL_SESSION_has_ticket(const SSL_SESSION *s)
|
||||||
|
{
|
||||||
|
return (s->tlsext_ticklen > 0) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned long
|
||||||
|
SSL_SESSION_get_ticket_lifetime_hint(const SSL_SESSION *s)
|
||||||
|
{
|
||||||
|
return s->tlsext_tick_lifetime_hint;
|
||||||
|
}
|
||||||
|
|
||||||
#endif /* OpenSSL < 1.1.0 or LibreSSL */
|
#endif /* OpenSSL < 1.1.0 or LibreSSL */
|
||||||
|
|
||||||
|
|
||||||
|
@ -293,25 +306,35 @@ typedef struct {
|
||||||
int eof_written;
|
int eof_written;
|
||||||
} PySSLMemoryBIO;
|
} PySSLMemoryBIO;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
PyObject_HEAD
|
||||||
|
SSL_SESSION *session;
|
||||||
|
PySSLContext *ctx;
|
||||||
|
} PySSLSession;
|
||||||
|
|
||||||
static PyTypeObject PySSLContext_Type;
|
static PyTypeObject PySSLContext_Type;
|
||||||
static PyTypeObject PySSLSocket_Type;
|
static PyTypeObject PySSLSocket_Type;
|
||||||
static PyTypeObject PySSLMemoryBIO_Type;
|
static PyTypeObject PySSLMemoryBIO_Type;
|
||||||
|
static PyTypeObject PySSLSession_Type;
|
||||||
|
|
||||||
/*[clinic input]
|
/*[clinic input]
|
||||||
module _ssl
|
module _ssl
|
||||||
class _ssl._SSLContext "PySSLContext *" "&PySSLContext_Type"
|
class _ssl._SSLContext "PySSLContext *" "&PySSLContext_Type"
|
||||||
class _ssl._SSLSocket "PySSLSocket *" "&PySSLSocket_Type"
|
class _ssl._SSLSocket "PySSLSocket *" "&PySSLSocket_Type"
|
||||||
class _ssl.MemoryBIO "PySSLMemoryBIO *" "&PySSLMemoryBIO_Type"
|
class _ssl.MemoryBIO "PySSLMemoryBIO *" "&PySSLMemoryBIO_Type"
|
||||||
|
class _ssl.SSLSession "PySSLSession *" "&PySSLSession_Type"
|
||||||
[clinic start generated code]*/
|
[clinic start generated code]*/
|
||||||
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=7bf7cb832638e2e1]*/
|
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=bdc67fafeeaa8109]*/
|
||||||
|
|
||||||
#include "clinic/_ssl.c.h"
|
#include "clinic/_ssl.c.h"
|
||||||
|
|
||||||
static int PySSL_select(PySocketSockObject *s, int writing, _PyTime_t timeout);
|
static int PySSL_select(PySocketSockObject *s, int writing, _PyTime_t timeout);
|
||||||
|
|
||||||
|
|
||||||
#define PySSLContext_Check(v) (Py_TYPE(v) == &PySSLContext_Type)
|
#define PySSLContext_Check(v) (Py_TYPE(v) == &PySSLContext_Type)
|
||||||
#define PySSLSocket_Check(v) (Py_TYPE(v) == &PySSLSocket_Type)
|
#define PySSLSocket_Check(v) (Py_TYPE(v) == &PySSLSocket_Type)
|
||||||
#define PySSLMemoryBIO_Check(v) (Py_TYPE(v) == &PySSLMemoryBIO_Type)
|
#define PySSLMemoryBIO_Check(v) (Py_TYPE(v) == &PySSLMemoryBIO_Type)
|
||||||
|
#define PySSLSession_Check(v) (Py_TYPE(v) == &PySSLSession_Type)
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
SOCKET_IS_NONBLOCKING,
|
SOCKET_IS_NONBLOCKING,
|
||||||
|
@ -2325,6 +2348,152 @@ _ssl__SSLSocket_tls_unique_cb_impl(PySSLSocket *self)
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef OPENSSL_VERSION_1_1
|
||||||
|
|
||||||
|
static SSL_SESSION*
|
||||||
|
_ssl_session_dup(SSL_SESSION *session) {
|
||||||
|
SSL_SESSION *newsession = NULL;
|
||||||
|
int slen;
|
||||||
|
unsigned char *senc = NULL, *p;
|
||||||
|
const unsigned char *const_p;
|
||||||
|
|
||||||
|
if (session == NULL) {
|
||||||
|
PyErr_SetString(PyExc_ValueError, "Invalid session");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* get length */
|
||||||
|
slen = i2d_SSL_SESSION(session, NULL);
|
||||||
|
if (slen == 0 || slen > 0xFF00) {
|
||||||
|
PyErr_SetString(PyExc_ValueError, "i2d() failed.");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
if ((senc = PyMem_Malloc(slen)) == NULL) {
|
||||||
|
PyErr_NoMemory();
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
p = senc;
|
||||||
|
if (!i2d_SSL_SESSION(session, &p)) {
|
||||||
|
PyErr_SetString(PyExc_ValueError, "i2d() failed.");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
const_p = senc;
|
||||||
|
newsession = d2i_SSL_SESSION(NULL, &const_p, slen);
|
||||||
|
if (session == NULL) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
PyMem_Free(senc);
|
||||||
|
return newsession;
|
||||||
|
error:
|
||||||
|
if (senc != NULL) {
|
||||||
|
PyMem_Free(senc);
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
PySSL_get_session(PySSLSocket *self, void *closure) {
|
||||||
|
/* get_session can return sessions from a server-side connection,
|
||||||
|
* it does not check for handshake done or client socket. */
|
||||||
|
PySSLSession *pysess;
|
||||||
|
SSL_SESSION *session;
|
||||||
|
|
||||||
|
#ifdef OPENSSL_VERSION_1_1
|
||||||
|
/* duplicate session as workaround for session bug in OpenSSL 1.1.0,
|
||||||
|
* https://github.com/openssl/openssl/issues/1550 */
|
||||||
|
session = SSL_get0_session(self->ssl); /* borrowed reference */
|
||||||
|
if (session == NULL) {
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
if ((session = _ssl_session_dup(session)) == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
session = SSL_get1_session(self->ssl);
|
||||||
|
if (session == NULL) {
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
pysess = PyObject_New(PySSLSession, &PySSLSession_Type);
|
||||||
|
if (pysess == NULL) {
|
||||||
|
SSL_SESSION_free(session);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(self->ctx);
|
||||||
|
pysess->ctx = self->ctx;
|
||||||
|
Py_INCREF(pysess->ctx);
|
||||||
|
pysess->session = session;
|
||||||
|
return (PyObject *)pysess;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int PySSL_set_session(PySSLSocket *self, PyObject *value,
|
||||||
|
void *closure)
|
||||||
|
{
|
||||||
|
PySSLSession *pysess;
|
||||||
|
#ifdef OPENSSL_VERSION_1_1
|
||||||
|
SSL_SESSION *session;
|
||||||
|
#endif
|
||||||
|
int result;
|
||||||
|
|
||||||
|
if (!PySSLSession_Check(value)) {
|
||||||
|
PyErr_SetString(PyExc_TypeError, "Value is not a SSLSession.");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
pysess = (PySSLSession *)value;
|
||||||
|
|
||||||
|
if (self->ctx->ctx != pysess->ctx->ctx) {
|
||||||
|
PyErr_SetString(PyExc_ValueError,
|
||||||
|
"Session refers to a different SSLContext.");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (self->socket_type != PY_SSL_CLIENT) {
|
||||||
|
PyErr_SetString(PyExc_ValueError,
|
||||||
|
"Cannot set session for server-side SSLSocket.");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (self->handshake_done) {
|
||||||
|
PyErr_SetString(PyExc_ValueError,
|
||||||
|
"Cannot set session after handshake.");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
#ifdef OPENSSL_VERSION_1_1
|
||||||
|
/* duplicate session */
|
||||||
|
if ((session = _ssl_session_dup(pysess->session)) == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
result = SSL_set_session(self->ssl, session);
|
||||||
|
/* free duplicate, SSL_set_session() bumps ref count */
|
||||||
|
SSL_SESSION_free(session);
|
||||||
|
#else
|
||||||
|
result = SSL_set_session(self->ssl, pysess->session);
|
||||||
|
#endif
|
||||||
|
if (result == 0) {
|
||||||
|
_setSSLError(NULL, 0, __FILE__, __LINE__);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyDoc_STRVAR(PySSL_set_session_doc,
|
||||||
|
"_setter_session(session)\n\
|
||||||
|
\
|
||||||
|
Get / set SSLSession.");
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
PySSL_get_session_reused(PySSLSocket *self, void *closure) {
|
||||||
|
if (SSL_session_reused(self->ssl)) {
|
||||||
|
Py_RETURN_TRUE;
|
||||||
|
} else {
|
||||||
|
Py_RETURN_FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PyDoc_STRVAR(PySSL_get_session_reused_doc,
|
||||||
|
"Was the client session reused during handshake?");
|
||||||
|
|
||||||
static PyGetSetDef ssl_getsetlist[] = {
|
static PyGetSetDef ssl_getsetlist[] = {
|
||||||
{"context", (getter) PySSL_get_context,
|
{"context", (getter) PySSL_get_context,
|
||||||
(setter) PySSL_set_context, PySSL_set_context_doc},
|
(setter) PySSL_set_context, PySSL_set_context_doc},
|
||||||
|
@ -2334,6 +2503,10 @@ static PyGetSetDef ssl_getsetlist[] = {
|
||||||
PySSL_get_server_hostname_doc},
|
PySSL_get_server_hostname_doc},
|
||||||
{"owner", (getter) PySSL_get_owner, (setter) PySSL_set_owner,
|
{"owner", (getter) PySSL_get_owner, (setter) PySSL_set_owner,
|
||||||
PySSL_get_owner_doc},
|
PySSL_get_owner_doc},
|
||||||
|
{"session", (getter) PySSL_get_session,
|
||||||
|
(setter) PySSL_set_session, PySSL_set_session_doc},
|
||||||
|
{"session_reused", (getter) PySSL_get_session_reused, NULL,
|
||||||
|
PySSL_get_session_reused_doc},
|
||||||
{NULL}, /* sentinel */
|
{NULL}, /* sentinel */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2618,7 +2791,7 @@ _ssl__SSLContext_get_ciphers_impl(PySSLContext *self)
|
||||||
{
|
{
|
||||||
SSL *ssl = NULL;
|
SSL *ssl = NULL;
|
||||||
STACK_OF(SSL_CIPHER) *sk = NULL;
|
STACK_OF(SSL_CIPHER) *sk = NULL;
|
||||||
SSL_CIPHER *cipher;
|
const SSL_CIPHER *cipher;
|
||||||
int i=0;
|
int i=0;
|
||||||
PyObject *result = NULL, *dct;
|
PyObject *result = NULL, *dct;
|
||||||
|
|
||||||
|
@ -4086,6 +4259,193 @@ static PyTypeObject PySSLMemoryBIO_Type = {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SSL Session object
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void
|
||||||
|
PySSLSession_dealloc(PySSLSession *self)
|
||||||
|
{
|
||||||
|
Py_XDECREF(self->ctx);
|
||||||
|
if (self->session != NULL) {
|
||||||
|
SSL_SESSION_free(self->session);
|
||||||
|
}
|
||||||
|
PyObject_Del(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
PySSLSession_richcompare(PyObject *left, PyObject *right, int op)
|
||||||
|
{
|
||||||
|
int result;
|
||||||
|
|
||||||
|
if (left == NULL || right == NULL) {
|
||||||
|
PyErr_BadInternalCall();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!PySSLSession_Check(left) || !PySSLSession_Check(right)) {
|
||||||
|
Py_RETURN_NOTIMPLEMENTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (left == right) {
|
||||||
|
result = 0;
|
||||||
|
} else {
|
||||||
|
const unsigned char *left_id, *right_id;
|
||||||
|
unsigned int left_len, right_len;
|
||||||
|
left_id = SSL_SESSION_get_id(((PySSLSession *)left)->session,
|
||||||
|
&left_len);
|
||||||
|
right_id = SSL_SESSION_get_id(((PySSLSession *)right)->session,
|
||||||
|
&right_len);
|
||||||
|
if (left_len == right_len) {
|
||||||
|
result = memcmp(left_id, right_id, left_len);
|
||||||
|
} else {
|
||||||
|
result = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (op) {
|
||||||
|
case Py_EQ:
|
||||||
|
if (result == 0) {
|
||||||
|
Py_RETURN_TRUE;
|
||||||
|
} else {
|
||||||
|
Py_RETURN_FALSE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Py_NE:
|
||||||
|
if (result != 0) {
|
||||||
|
Py_RETURN_TRUE;
|
||||||
|
} else {
|
||||||
|
Py_RETURN_FALSE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Py_LT:
|
||||||
|
case Py_LE:
|
||||||
|
case Py_GT:
|
||||||
|
case Py_GE:
|
||||||
|
Py_RETURN_NOTIMPLEMENTED;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
PyErr_BadArgument();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
PySSLSession_traverse(PySSLSession *self, visitproc visit, void *arg)
|
||||||
|
{
|
||||||
|
Py_VISIT(self->ctx);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
PySSLSession_clear(PySSLSession *self)
|
||||||
|
{
|
||||||
|
Py_CLEAR(self->ctx);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
PySSLSession_get_time(PySSLSession *self, void *closure) {
|
||||||
|
return PyLong_FromLong(SSL_SESSION_get_time(self->session));
|
||||||
|
}
|
||||||
|
|
||||||
|
PyDoc_STRVAR(PySSLSession_get_time_doc,
|
||||||
|
"Session creation time (seconds since epoch).");
|
||||||
|
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
PySSLSession_get_timeout(PySSLSession *self, void *closure) {
|
||||||
|
return PyLong_FromLong(SSL_SESSION_get_timeout(self->session));
|
||||||
|
}
|
||||||
|
|
||||||
|
PyDoc_STRVAR(PySSLSession_get_timeout_doc,
|
||||||
|
"Session timeout (delta in seconds).");
|
||||||
|
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
PySSLSession_get_ticket_lifetime_hint(PySSLSession *self, void *closure) {
|
||||||
|
unsigned long hint = SSL_SESSION_get_ticket_lifetime_hint(self->session);
|
||||||
|
return PyLong_FromUnsignedLong(hint);
|
||||||
|
}
|
||||||
|
|
||||||
|
PyDoc_STRVAR(PySSLSession_get_ticket_lifetime_hint_doc,
|
||||||
|
"Ticket life time hint.");
|
||||||
|
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
PySSLSession_get_session_id(PySSLSession *self, void *closure) {
|
||||||
|
const unsigned char *id;
|
||||||
|
unsigned int len;
|
||||||
|
id = SSL_SESSION_get_id(self->session, &len);
|
||||||
|
return PyBytes_FromStringAndSize((const char *)id, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
PyDoc_STRVAR(PySSLSession_get_session_id_doc,
|
||||||
|
"Session id");
|
||||||
|
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
PySSLSession_get_has_ticket(PySSLSession *self, void *closure) {
|
||||||
|
if (SSL_SESSION_has_ticket(self->session)) {
|
||||||
|
Py_RETURN_TRUE;
|
||||||
|
} else {
|
||||||
|
Py_RETURN_FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PyDoc_STRVAR(PySSLSession_get_has_ticket_doc,
|
||||||
|
"Does the session contain a ticket?");
|
||||||
|
|
||||||
|
|
||||||
|
static PyGetSetDef PySSLSession_getsetlist[] = {
|
||||||
|
{"has_ticket", (getter) PySSLSession_get_has_ticket, NULL,
|
||||||
|
PySSLSession_get_has_ticket_doc},
|
||||||
|
{"id", (getter) PySSLSession_get_session_id, NULL,
|
||||||
|
PySSLSession_get_session_id_doc},
|
||||||
|
{"ticket_lifetime_hint", (getter) PySSLSession_get_ticket_lifetime_hint,
|
||||||
|
NULL, PySSLSession_get_ticket_lifetime_hint_doc},
|
||||||
|
{"time", (getter) PySSLSession_get_time, NULL,
|
||||||
|
PySSLSession_get_time_doc},
|
||||||
|
{"timeout", (getter) PySSLSession_get_timeout, NULL,
|
||||||
|
PySSLSession_get_timeout_doc},
|
||||||
|
{NULL}, /* sentinel */
|
||||||
|
};
|
||||||
|
|
||||||
|
static PyTypeObject PySSLSession_Type = {
|
||||||
|
PyVarObject_HEAD_INIT(NULL, 0)
|
||||||
|
"_ssl.Session", /*tp_name*/
|
||||||
|
sizeof(PySSLSession), /*tp_basicsize*/
|
||||||
|
0, /*tp_itemsize*/
|
||||||
|
(destructor)PySSLSession_dealloc, /*tp_dealloc*/
|
||||||
|
0, /*tp_print*/
|
||||||
|
0, /*tp_getattr*/
|
||||||
|
0, /*tp_setattr*/
|
||||||
|
0, /*tp_reserved*/
|
||||||
|
0, /*tp_repr*/
|
||||||
|
0, /*tp_as_number*/
|
||||||
|
0, /*tp_as_sequence*/
|
||||||
|
0, /*tp_as_mapping*/
|
||||||
|
0, /*tp_hash*/
|
||||||
|
0, /*tp_call*/
|
||||||
|
0, /*tp_str*/
|
||||||
|
0, /*tp_getattro*/
|
||||||
|
0, /*tp_setattro*/
|
||||||
|
0, /*tp_as_buffer*/
|
||||||
|
Py_TPFLAGS_DEFAULT, /*tp_flags*/
|
||||||
|
0, /*tp_doc*/
|
||||||
|
(traverseproc)PySSLSession_traverse, /*tp_traverse*/
|
||||||
|
(inquiry)PySSLSession_clear, /*tp_clear*/
|
||||||
|
PySSLSession_richcompare, /*tp_richcompare*/
|
||||||
|
0, /*tp_weaklistoffset*/
|
||||||
|
0, /*tp_iter*/
|
||||||
|
0, /*tp_iternext*/
|
||||||
|
0, /*tp_methods*/
|
||||||
|
0, /*tp_members*/
|
||||||
|
PySSLSession_getsetlist, /*tp_getset*/
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/* helper routines for seeding the SSL PRNG */
|
/* helper routines for seeding the SSL PRNG */
|
||||||
/*[clinic input]
|
/*[clinic input]
|
||||||
_ssl.RAND_add
|
_ssl.RAND_add
|
||||||
|
@ -4771,6 +5131,9 @@ PyInit__ssl(void)
|
||||||
return NULL;
|
return NULL;
|
||||||
if (PyType_Ready(&PySSLMemoryBIO_Type) < 0)
|
if (PyType_Ready(&PySSLMemoryBIO_Type) < 0)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
if (PyType_Ready(&PySSLSession_Type) < 0)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
|
||||||
m = PyModule_Create(&_sslmodule);
|
m = PyModule_Create(&_sslmodule);
|
||||||
if (m == NULL)
|
if (m == NULL)
|
||||||
|
@ -4842,6 +5205,10 @@ PyInit__ssl(void)
|
||||||
if (PyDict_SetItemString(d, "MemoryBIO",
|
if (PyDict_SetItemString(d, "MemoryBIO",
|
||||||
(PyObject *)&PySSLMemoryBIO_Type) != 0)
|
(PyObject *)&PySSLMemoryBIO_Type) != 0)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
if (PyDict_SetItemString(d, "SSLSession",
|
||||||
|
(PyObject *)&PySSLSession_Type) != 0)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
PyModule_AddIntConstant(m, "SSL_ERROR_ZERO_RETURN",
|
PyModule_AddIntConstant(m, "SSL_ERROR_ZERO_RETURN",
|
||||||
PY_SSL_ERROR_ZERO_RETURN);
|
PY_SSL_ERROR_ZERO_RETURN);
|
||||||
PyModule_AddIntConstant(m, "SSL_ERROR_WANT_READ",
|
PyModule_AddIntConstant(m, "SSL_ERROR_WANT_READ",
|
||||||
|
@ -4968,6 +5335,7 @@ PyInit__ssl(void)
|
||||||
PyModule_AddIntConstant(m, "OP_CIPHER_SERVER_PREFERENCE",
|
PyModule_AddIntConstant(m, "OP_CIPHER_SERVER_PREFERENCE",
|
||||||
SSL_OP_CIPHER_SERVER_PREFERENCE);
|
SSL_OP_CIPHER_SERVER_PREFERENCE);
|
||||||
PyModule_AddIntConstant(m, "OP_SINGLE_DH_USE", SSL_OP_SINGLE_DH_USE);
|
PyModule_AddIntConstant(m, "OP_SINGLE_DH_USE", SSL_OP_SINGLE_DH_USE);
|
||||||
|
PyModule_AddIntConstant(m, "OP_NO_TICKET", SSL_OP_NO_TICKET);
|
||||||
#ifdef SSL_OP_SINGLE_ECDH_USE
|
#ifdef SSL_OP_SINGLE_ECDH_USE
|
||||||
PyModule_AddIntConstant(m, "OP_SINGLE_ECDH_USE", SSL_OP_SINGLE_ECDH_USE);
|
PyModule_AddIntConstant(m, "OP_SINGLE_ECDH_USE", SSL_OP_SINGLE_ECDH_USE);
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Reference in New Issue