diff -r 1d14675c6050 Doc/library/ssl.rst --- a/Doc/library/ssl.rst Thu Oct 08 06:34:31 2015 +0300 +++ b/Doc/library/ssl.rst Thu Oct 08 11:31:18 2015 +0200 @@ -138,7 +138,7 @@ Python 3.2, it can be more flexible to use :meth:`SSLContext.wrap_socket` instead. -.. function:: wrap_socket(sock, keyfile=None, certfile=None, server_side=False, cert_reqs=CERT_NONE, ssl_version={see docs}, ca_certs=None, do_handshake_on_connect=True, suppress_ragged_eofs=True, ciphers=None) +.. function:: wrap_socket(sock, keyfile=None, certfile=None, server_side=False, cert_reqs=CERT_NONE, ssl_version={see docs}, ca_certs=None, do_handshake_on_connect=True, suppress_ragged_eofs=True, ciphers=None, session=None) Takes an instance ``sock`` of :class:`socket.socket`, and returns an instance of :class:`ssl.SSLSocket`, a subtype of :class:`socket.socket`, which wraps @@ -222,9 +222,15 @@ raised from the underlying socket; if :const:`False`, it will raise the exceptions back to the caller. + The parameter ``session`` can be used to pass a specific session for session + reuse. + .. versionchanged:: 3.2 New optional argument *ciphers*. + .. versionchanged:: 3.6 + New optional argument *session*. + Context creation ^^^^^^^^^^^^^^^^ @@ -901,6 +907,10 @@ The socket timeout is no more reset each time bytes are received or sent. The socket timeout is now to maximum total duration of the handshake. + .. versionchanged:: 3.6 + Will try to reuse a SSL session if one has been provided. + :attr:`~SSLSocket.session` will be set. + .. method:: SSLSocket.getpeercert(binary_form=False) If there is no certificate for the peer on the other end of the connection, @@ -1059,6 +1069,13 @@ .. versionadded:: 3.2 +.. attribute:: SSLSocket.session + + The SSLSession object used by this SSL socket. Set this to yield session + reuse. + + .. versionadded:: 3.6 + .. attribute:: SSLSocket.server_side A boolean which is ``True`` for server-side sockets and ``False`` for @@ -1853,6 +1870,7 @@ The following methods are available: - :attr:`~SSLSocket.context` + - :attr:`~SSLSocket.session` - :attr:`~SSLSocket.server_side` - :attr:`~SSLSocket.server_hostname` - :meth:`~SSLSocket.read` diff -r 1d14675c6050 Lib/ftplib.py --- a/Lib/ftplib.py Thu Oct 08 06:34:31 2015 +0300 +++ b/Lib/ftplib.py Thu Oct 08 11:31:18 2015 +0200 @@ -792,8 +792,12 @@ def ntransfercmd(self, cmd, rest=None): conn, size = FTP.ntransfercmd(self, cmd, rest) if self._prot_p: + session = None + if isinstance(self.sock, ssl.SSLSocket): + session = self.sock.session conn = self.context.wrap_socket(conn, - server_hostname=self.host) + server_hostname=self.host, + session=session) return conn, size def abort(self): diff -r 1d14675c6050 Lib/ssl.py --- a/Lib/ssl.py Thu Oct 08 06:34:31 2015 +0300 +++ b/Lib/ssl.py Thu Oct 08 11:31:18 2015 +0200 @@ -368,12 +368,12 @@ def wrap_socket(self, sock, server_side=False, do_handshake_on_connect=True, suppress_ragged_eofs=True, - server_hostname=None): + server_hostname=None, session=None): return SSLSocket(sock=sock, server_side=server_side, do_handshake_on_connect=do_handshake_on_connect, suppress_ragged_eofs=suppress_ragged_eofs, server_hostname=server_hostname, - _context=self) + _context=self, _session=session) def wrap_bio(self, incoming, outgoing, server_side=False, server_hostname=None): @@ -550,6 +550,15 @@ self._sslobj.context = ctx @property + def session(self): + """The SSLSession that is currently in use.""" + return self._sslobj.session + + @session.setter + def session(self, session): + self._sslobj.session = session + + @property def server_side(self): """Whether this is a server-side socket.""" return self._sslobj.server_side @@ -666,7 +675,7 @@ family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None, suppress_ragged_eofs=True, npn_protocols=None, ciphers=None, server_hostname=None, - _context=None): + _context=None, _session=None): if _context: self._context = _context @@ -694,6 +703,7 @@ self.ssl_version = ssl_version self.ca_certs = ca_certs self.ciphers = ciphers + self._session = _session # Can't use sock.type as other flags (such as SOCK_NONBLOCK) get # mixed in. if sock.getsockopt(SOL_SOCKET, SO_TYPE) != SOCK_STREAM: @@ -759,6 +769,15 @@ self._context = ctx self._sslobj.context = ctx + @property + def session(self): + return self._session + + @session.setter + def session(self, session): + self._session = session + self._sslobj.session = session + def dup(self): raise NotImplemented("Can't dup() %s instances" % self.__class__.__name__) @@ -977,11 +996,15 @@ """Perform a TLS/SSL handshake.""" self._check_connected() timeout = self.gettimeout() + # set session before doing the handshake if provided + if self._session: + self._sslobj.session = self._session try: if timeout == 0.0 and block: self.settimeout(None) self._sslobj.do_handshake() finally: + self._session = self._sslobj.session self.settimeout(timeout) def _real_connect(self, addr, connect_ex): @@ -1054,14 +1077,14 @@ ssl_version=PROTOCOL_SSLv23, ca_certs=None, do_handshake_on_connect=True, suppress_ragged_eofs=True, - ciphers=None): + ciphers=None, session=None): return SSLSocket(sock=sock, keyfile=keyfile, certfile=certfile, server_side=server_side, cert_reqs=cert_reqs, ssl_version=ssl_version, ca_certs=ca_certs, do_handshake_on_connect=do_handshake_on_connect, suppress_ragged_eofs=suppress_ragged_eofs, - ciphers=ciphers) + ciphers=ciphers, _session=session) # some utility functions diff -r 1d14675c6050 Modules/_ssl.c --- a/Modules/_ssl.c Thu Oct 08 06:34:31 2015 +0300 +++ b/Modules/_ssl.c Thu Oct 08 11:31:18 2015 +0200 @@ -212,12 +212,18 @@ typedef struct { PyObject_HEAD + SSL_SESSION *session; +} PySSLSession; + +typedef struct { + PyObject_HEAD BIO *bio; int eof_written; } PySSLMemoryBIO; static PyTypeObject PySSLContext_Type; static PyTypeObject PySSLSocket_Type; +static PyTypeObject PySSLSession_Type; static PyTypeObject PySSLMemoryBIO_Type; /*[clinic input] @@ -234,6 +240,7 @@ #define PySSLContext_Check(v) (Py_TYPE(v) == &PySSLContext_Type) #define PySSLSocket_Check(v) (Py_TYPE(v) == &PySSLSocket_Type) +#define PySSLSession_Check(v) (Py_TYPE(v) == &PySSLSession_Type) #define PySSLMemoryBIO_Check(v) (Py_TYPE(v) == &PySSLMemoryBIO_Type) typedef enum { @@ -1610,6 +1617,33 @@ on the SSLContext to change the certificate information associated with the\n\ SSLSocket before the cryptographic exchange handshake messages\n"); +static PySSLSession * PySSL_get_session(PySSLSocket *self, void *closure) { + PySSLSession *session; + + session = PyObject_New(PySSLSession, &PySSLSession_Type); + if (session == NULL) + return NULL; + + session->session = SSL_get1_session(self->ssl); + return session; +} + +static int PySSL_set_session(PySSLSocket *self, PyObject *value, + void *closure) { + if (!PyObject_TypeCheck(value, &PySSLSession_Type)) { + PyErr_SetString(PyExc_TypeError, "The value must be a SSLSession"); + return -1; + } + SSL_set_session(self->ssl, ((PySSLSession *) value)->session); + return 0; +} + +PyDoc_STRVAR(PySSL_set_session_doc, +"_setter_session(session)\n\ +\ +This changes the underlying session object. Setting an already established\n\ +session to a new socket will yield session reuse upon connection.\n"); + static PyObject * PySSL_get_server_side(PySSLSocket *self, void *c) @@ -2143,6 +2177,8 @@ static PyGetSetDef ssl_getsetlist[] = { {"context", (getter) PySSL_get_context, (setter) PySSL_set_context, PySSL_set_context_doc}, + {"session", (getter) PySSL_get_session, + (setter) PySSL_set_session, PySSL_set_session_doc}, {"server_side", (getter) PySSL_get_server_side, NULL, PySSL_get_server_side_doc}, {"server_hostname", (getter) PySSL_get_server_hostname, NULL, @@ -3574,6 +3610,59 @@ _ssl__SSLContext, /*tp_new*/ }; +/* + * _SSLSession objects + */ + +static void +session_dealloc(PySSLSession *self) +{ + SSL_SESSION_free(self->session); + PyObject_Del(self); +} + + +static PyTypeObject PySSLSession_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "_ssl._SSLSession", /*tp_name*/ + sizeof(PySSLSession), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + (destructor)session_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*/ + 0, /*tp_flags*/ + 0, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + 0, /*tp_methods*/ + 0, /*tp_members*/ + 0, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + 0, /*tp_init*/ + 0, /*tp_alloc*/ + 0, /*tp_new*/ +}; + /* * MemoryBIO objects @@ -4470,6 +4559,8 @@ if (PyType_Ready(&PySSLContext_Type) < 0) return NULL; + if (PyType_Ready(&PySSLSession_Type) < 0) + return NULL; if (PyType_Ready(&PySSLSocket_Type) < 0) return NULL; if (PyType_Ready(&PySSLMemoryBIO_Type) < 0)