diff -r 37aee118e1a3 Doc/library/ssl.rst --- a/Doc/library/ssl.rst Tue Oct 06 23:12:02 2015 -0400 +++ b/Doc/library/ssl.rst Wed Oct 07 13:46:48 2015 +0200 @@ -138,7 +138,7 @@ Python 2.7.9, 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,14 @@ 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 an already established session + in order to yield session reuse. + .. versionchanged:: 2.7 New optional argument *ciphers*. + .. versionchanged:: 2.7.11 + New optional argument *session* Context creation ^^^^^^^^^^^^^^^^ @@ -928,6 +933,14 @@ .. versionadded:: 2.7.9 +.. attribute:: SSLSocket.session + + Returns the underlying SSL session object which can be passed to + :func:`wrap_socket` or :meth:`SSLContext.wrap_socket`. Passing an already + established session will yield session reuse. + + .. versionadded:: 2.7.11 + SSL Contexts ------------ @@ -1167,7 +1180,7 @@ .. method:: SSLContext.wrap_socket(sock, server_side=False, \ 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` object. *sock* must be a :data:`~socket.SOCK_STREAM` socket; other socket @@ -1184,10 +1197,16 @@ quite similarly to HTTP virtual hosts. Specifying *server_hostname* will raise a :exc:`ValueError` if *server_side* is true. + The parameter *session* can be used to pass an already established session + in order to yield session reuse. + .. versionchanged:: 2.7.9 Always allow a server_hostname to be passed, even if OpenSSL does not have SNI. + .. versionchanged:: 2.7.11 + New optional argument *session* + .. method:: SSLContext.session_stats() Get statistics about the SSL sessions created or managed by this context. diff -r 37aee118e1a3 Lib/ftplib.py --- a/Lib/ftplib.py Tue Oct 06 23:12:02 2015 -0400 +++ b/Lib/ftplib.py Wed Oct 07 13:46:48 2015 +0200 @@ -709,8 +709,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 retrbinary(self, cmd, callback, blocksize=8192, rest=None): diff -r 37aee118e1a3 Lib/ssl.py --- a/Lib/ssl.py Tue Oct 06 23:12:02 2015 -0400 +++ b/Lib/ssl.py Wed Oct 07 13:46:48 2015 +0200 @@ -344,11 +344,13 @@ 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, + session=session, _context=self) def set_npn_protocols(self, npn_protocols): @@ -500,7 +502,7 @@ do_handshake_on_connect=True, family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None, suppress_ragged_eofs=True, npn_protocols=None, ciphers=None, - server_hostname=None, + server_hostname=None, session=None, _context=None): self._makefile_refs = 0 @@ -571,6 +573,11 @@ try: self._sslobj = self._context._wrap_socket(self._sock, server_side, server_hostname, ssl_sock=self) + + # reuse session + if session: + self.session = session + if do_handshake_on_connect: timeout = self.gettimeout() if timeout == 0.0: @@ -591,6 +598,14 @@ self._context = ctx self._sslobj.context = ctx + @property + def session(self): + return self._sslobj.session + + @session.setter + def session(self, session): + self._sslobj.session = session + def dup(self): raise NotImplemented("Can't dup() %s instances" % self.__class__.__name__) @@ -901,14 +916,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 37aee118e1a3 Modules/_ssl.c --- a/Modules/_ssl.c Tue Oct 06 23:12:02 2015 -0400 +++ b/Modules/_ssl.c Wed Oct 07 13:46:48 2015 +0200 @@ -235,8 +235,14 @@ enum py_ssl_server_or_client socket_type; } PySSLSocket; +typedef struct { + PyObject_HEAD + SSL_SESSION *session; +} PySSLSession; + static PyTypeObject PySSLContext_Type; static PyTypeObject PySSLSocket_Type; +static PyTypeObject PySSLSession_Type; static PyObject *PySSL_SSLwrite(PySSLSocket *self, PyObject *args); static PyObject *PySSL_SSLread(PySSLSocket *self, PyObject *args); @@ -247,6 +253,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) typedef enum { SOCKET_IS_NONBLOCKING, @@ -1488,6 +1495,34 @@ 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 void PySSL_dealloc(PySSLSocket *self) @@ -1909,6 +1944,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}, {NULL}, /* sentinel */ }; @@ -3331,6 +3368,53 @@ }; +/* + * _SSLSession objects + */ + +static void +PySSLSession_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*/ + /* methods */ + (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*/ + 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*/ +}; + + #ifdef HAVE_OPENSSL_RAND @@ -3947,6 +4031,8 @@ return; if (PyType_Ready(&PySSLSocket_Type) < 0) return; + if (PyType_Ready(&PySSLSession_Type) < 0) + return; m = Py_InitModule3("_ssl", PySSL_methods, module_doc); if (m == NULL)