cpython: a79003f25a41 (original) (raw)

--- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -803,6 +803,29 @@ the specification of normal, OS-level so SSL sockets also have the following additional methods and attributes: +.. method:: SSLSocket.read(len=0, buffer=None) +

+.. method:: SSLSocket.write(buf) +

+.. note:: +

.. method:: SSLSocket.do_handshake() Perform the SSL setup handshake. @@ -935,6 +958,11 @@ SSL sockets also have the following addi .. versionadded:: 3.5 +.. method:: SSLSocket.pending() +

.. attribute:: SSLSocket.context The :class:SSLContext object this SSL socket is tied to. If the SSL @@ -944,6 +972,22 @@ SSL sockets also have the following addi .. versionadded:: 3.2 +.. attribute:: SSLSocket.server_side +

+.. attribute:: SSLSocket.server_hostname +

SSL Contexts ------------ @@ -1670,6 +1714,130 @@ thus several things you need to be aware select.select([], [sock], []) +Memory BIO Support +------------------ + +.. versionadded:: 3.5 + +Ever since the SSL module was introduced in Python 2.6, the :class:SSLSocket +class has provided two related but distinct areas of functionality: + +- SSL protocol handling +- Network IO + +The network IO API is identical to that provided by :class:socket.socket, +from which :class:SSLSocket also inherits. This allows an SSL socket to be +used as a drop-in replacement for a regular socket, making it very easy to add +SSL support to an existing application. + +Combining SSL protocol handling and network IO usually works well, but there +are some cases where it doesn't. An example is async IO frameworks that want to +use a different IO multiplexing model than the "select/poll on a file +descriptor" (readiness based) model that is assumed by :class:socket.socket +and by the internal OpenSSL socket IO routines. This is mostly relevant for +platforms like Windows where this model is not efficient. For this purpose, a +reduced scope variant of :class:SSLSocket called :class:SSLObject is +provided. + +.. class:: SSLObject +

+The following methods are available from :class:SSLSocket: + +- :attr:~SSLSocket.context +- :attr:~SSLSocket.server_side +- :attr:~SSLSocket.server_hostname +- :meth:~SSLSocket.read +- :meth:~SSLSocket.write +- :meth:~SSLSocket.getpeercert +- :meth:~SSLSocket.selected_npn_protocol +- :meth:~SSLSocket.cipher +- :meth:~SSLSocket.compression +- :meth:~SSLSocket.pending +- :meth:~SSLSocket.do_handshake +- :meth:~SSLSocket.unwrap +- :meth:~SSLSocket.get_channel_binding + +An SSLObject communicates with the outside world using memory buffers. The +class :class:MemoryBIO provides a memory buffer that can be used for this +purpose. It wraps an OpenSSL memory BIO (Basic IO) object: + +.. class:: MemoryBIO +

+.. attribute:: MemoryBIO.pending +

+.. attribute:: MemoryBIO.eof +

+.. method:: MemoryBIO.read(n=-1) +

+.. method:: MemoryBIO.write(buf) +

+.. method:: MemoryBIO.write_eof() +

+An :class:SSLObject instance can be created using the +:meth:~SSLContext.wrap_bio method. This method will create the +:class:SSLObject instance and bind it to a pair of BIOs. The incoming BIO +is used to pass data from Python to the SSL protocol instance, while the +outgoing BIO is used to pass data the other way around. + +.. method:: SSLContext.wrap_bio(incoming, outgoing, server_side=False, [](#l1.161)

+

+Some notes related to the use of :class:SSLObject: + +- All IO on an :class:SSLObject is non-blocking. This means that for example

--- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -97,7 +97,7 @@ from enum import Enum as _Enum, IntEnum 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 _SSLContext +from _ssl import _SSLContext, MemoryBIO from _ssl import ( SSLError, SSLZeroReturnError, SSLWantReadError, SSLWantWriteError, SSLSyscallError, SSLEOFError, @@ -352,6 +352,12 @@ class SSLContext(_SSLContext): server_hostname=server_hostname, _context=self)

+ def set_npn_protocols(self, npn_protocols): protos = bytearray() for protocol in npn_protocols: @@ -469,6 +475,129 @@ def _create_stdlib_context(protocol=PROT return context + +class SSLObject:

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+ + class SSLSocket(socket): """This class implements a subtype of socket.socket that wraps the underlying OS socket in an SSL context when necessary, and @@ -556,8 +685,9 @@ class SSLSocket(socket): if connected: # create the SSL object try:

@@ -602,11 +732,7 @@ class SSLSocket(socket): if not self._sslobj: raise ValueError("Read on closed or unwrapped SSL socket.") try:

@@ -633,7 +759,7 @@ class SSLSocket(socket): self._checkClosed() self._check_connected()

def selected_npn_protocol(self): self._checkClosed() @@ -773,7 +899,7 @@ class SSLSocket(socket): def unwrap(self): if self._sslobj:

@@ -794,12 +920,6 @@ class SSLSocket(socket): finally: self.settimeout(timeout)

- def _real_connect(self, addr, connect_ex): if self.server_side: raise ValueError("can't connect in server-side mode") @@ -807,7 +927,8 @@ class SSLSocket(socket): # connected at the time of the call. We connect it, then wrap it. if self._connected: raise ValueError("attempt to connect already-connected SSLSocket!")

@@ -850,15 +971,9 @@ class SSLSocket(socket): if the requested cb_type is not supported. Return bytes of the data or None if the data is not available (e.g. before the handshake). """

def version(self): """

--- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -518,9 +518,14 @@ class BasicSocketTests(unittest.TestCase def test_unknown_channel_binding(self): # should raise ValueError for unknown type s = socket.socket(socket.AF_INET)

@unittest.skipUnless("tls-unique" in ssl.CHANNEL_BINDING_TYPES, "'tls-unique' channel binding not available") @@ -1247,6 +1252,69 @@ class SSLErrorTests(unittest.TestCase): self.assertEqual(cm.exception.errno, ssl.SSL_ERROR_WANT_READ) +class MemoryBIOTests(unittest.TestCase): +

+

+

+

+

+ + class NetworkedTests(unittest.TestCase): def test_connect(self): @@ -1577,6 +1645,95 @@ class NetworkedTests(unittest.TestCase): self.assertIs(ss.context, ctx2) self.assertIs(ss._sslobj.context, ctx2) + +class NetworkedBIOTests(unittest.TestCase): +

+

+

+ + try: import threading except ImportError: @@ -3061,10 +3218,11 @@ def test_main(verbose=False): if not os.path.exists(filename): raise support.TestFailed("Can't read certificate file %r" % filename)

if support.is_resource_enabled('network'): tests.append(NetworkedTests)

if _have_threads: thread_info = support.threading_setup()

--- a/Misc/NEWS +++ b/Misc/NEWS @@ -166,6 +166,9 @@ Core and Builtins Library ------- +- Issue #21965: Add support for in-memory SSL to the ssl module. Patch

--- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -64,6 +64,7 @@ static PySocketModule_APIObject PySocket #include "openssl/ssl.h" #include "openssl/err.h" #include "openssl/rand.h" +#include "openssl/bio.h" /* SSL error object */ static PyObject *PySSLErrorObject; @@ -226,10 +227,19 @@ typedef struct { char shutdown_seen_zero; char handshake_done; enum py_ssl_server_or_client socket_type;

} PySSLSocket; +typedef struct {

+} PySSLMemoryBIO; + static PyTypeObject PySSLContext_Type; static PyTypeObject PySSLSocket_Type; +static PyTypeObject PySSLMemoryBIO_Type; static PyObject *PySSL_SSLwrite(PySSLSocket *self, PyObject *args); static PyObject *PySSL_SSLread(PySSLSocket *self, PyObject *args); @@ -240,6 +250,7 @@ static PyObject PySSL_cipher(PySSLSocke #define PySSLContext_Check(v) (Py_TYPE(v) == &PySSLContext_Type) #define PySSLSocket_Check(v) (Py_TYPE(v) == &PySSLSocket_Type) +#define PySSLMemoryBIO_Check(v) (Py_TYPE(v) == &PySSLMemoryBIO_Type) typedef enum { SOCKET_IS_NONBLOCKING, @@ -254,6 +265,9 @@ typedef enum { #define ERRSTR1(x,y,z) (x ":" y ": " z) #define ERRSTR(x) ERRSTR1("_ssl.c", Py_STRINGIFY(LINE), x) +/ Get the socket from a PySSLSocket, if it has one */ +#define GET_SOCKET(obj) ((obj)->Socket ? [](#l5.44)

/*

@@ -477,10 +490,12 @@ static PyObject * static PySSLSocket * newPySSLSocket(PySSLContext *sslctx, PySocketSockObject *sock, enum py_ssl_server_or_client socket_type,

{ PySSLSocket *self; SSL_CTX *ctx = sslctx->ctx;

+ Py_INCREF(sslctx); /* Make sure the SSL error state is initialized */ @@ -502,8 +529,17 @@ newPySSLSocket(PySSLContext *sslctx, PyS PySSL_BEGIN_ALLOW_THREADS self->ssl = SSL_new(ctx); PySSL_END_ALLOW_THREADS

#ifdef SSL_MODE_AUTO_RETRY mode |= SSL_MODE_AUTO_RETRY; @@ -518,7 +554,7 @@ newPySSLSocket(PySSLContext sslctx, PyS / If the socket is in non-blocking mode or timeout mode, set the BIO * to non-blocking mode (blocking is the default) */

-

+

+

-

/* Actually negotiate SSL connection / / XXX If SSL_do_handshake() returns 0, it's also a failure. */ @@ -593,7 +633,7 @@ static PyObject *PySSL_SSLdo_handshake(P break; } } while (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE);

@@ -608,7 +648,7 @@ static PyObject *PySSL_SSLdo_handshake(P return Py_None; error:

@@ -1483,6 +1523,54 @@ on the SSLContext to change the certific SSLSocket before the cryptographic exchange handshake messages\n"); +static PyObject * +PySSL_get_server_side(PySSLSocket *self, void *c) +{

+} + +PyDoc_STRVAR(PySSL_get_server_side_doc, +"Whether this is a server-side socket."); + +static PyObject * +PySSL_get_server_hostname(PySSLSocket *self, void *c) +{

+} + +PyDoc_STRVAR(PySSL_get_server_hostname_doc, +"The currently set server hostname (for SNI)."); + +static PyObject * +PySSL_get_owner(PySSLSocket *self, void *c) +{

+

+

+} + +static int +PySSL_set_owner(PySSLSocket *self, PyObject *value, void *c) +{

+} + +PyDoc_STRVAR(PySSL_get_owner_doc, +"The Python-level owner of this object.[](#l5.247) +Passed as "self" in servername callback."); + static void PySSL_dealloc(PySSLSocket *self) { @@ -1492,6 +1580,8 @@ static void PySSL_dealloc(PySSLSocket *s SSL_free(self->ssl); Py_XDECREF(self->Socket); Py_XDECREF(self->ctx);

@@ -1508,10 +1598,10 @@ check_socket_and_wait_for_timeout(PySock int rc; /* Nothing to do unless we're in timeout mode (not non-blocking) */

/* Guard against closed socket */ if (s->sock_fd < 0) @@ -1572,18 +1662,19 @@ static PyObject *PySSL_SSLwrite(PySSLSoc int sockstate; int err; int nonblocking;

-

+

if (!PyArg_ParseTuple(args, "y*:write", &buf)) {

sockstate = check_socket_and_wait_for_timeout(sock, 1); if (sockstate == SOCKET_HAS_TIMED_OUT) { @@ -1640,7 +1733,7 @@ static PyObject *PySSL_SSLwrite(PySSLSoc } } while (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE);

@@ -1648,7 +1741,7 @@ static PyObject *PySSL_SSLwrite(PySSLSoc return PySSL_SetError(self, len, FILE, LINE); error:

@@ -1688,15 +1781,16 @@ static PyObject *PySSL_SSLread(PySSLSock int sockstate; int err; int nonblocking;

-

+

buf.obj = NULL; buf.buf = NULL; @@ -1722,10 +1816,12 @@ static PyObject *PySSL_SSLread(PySSLSock } }

/* first check if there are bytes ready to be read */ PySSL_BEGIN_ALLOW_THREADS @@ -1781,7 +1877,7 @@ static PyObject *PySSL_SSLread(PySSLSock } done:

@@ -1792,7 +1888,7 @@ done: } error:

-

+

+

-

while (1) { PySSL_BEGIN_ALLOW_THREADS @@ -1881,15 +1978,17 @@ static PyObject *PySSL_SSLshutdown(PySSL } if (err < 0) {

error:

@@ -1937,6 +2036,12 @@ If the TLS handshake is not yet complete static PyGetSetDef ssl_getsetlist[] = { {"context", (getter) PySSL_get_context, (setter) PySSL_set_context, PySSL_set_context_doc},

#endif }

+

+#if HAVE_SNI

+#else

+#endif

+

+

+} + +static PyObject * session_stats(PySSLContext *self, PyObject *unused) { int r; @@ -2938,11 +3078,25 @@ static int ssl = SSL_get_app_data(s); assert(PySSLSocket_Check(ssl));

+

+ Py_INCREF(ssl_socket);

if (servername == NULL) { result = PyObject_CallFunctionObjArgs(ssl_ctx->set_hostname, ssl_socket, @@ -3171,6 +3325,8 @@ static PyGetSetDef context_getsetlist[] static struct PyMethodDef context_methods[] = { {"_wrap_socket", (PyCFunction) context_wrap_socket, METH_VARARGS | METH_KEYWORDS, NULL},

+

+

+

+

+} + +static void +memory_bio_dealloc(PySSLMemoryBIO *self) +{

+} + +static PyObject * +memory_bio_get_pending(PySSLMemoryBIO *self, void *c) +{

+} + +PyDoc_STRVAR(PySSL_memory_bio_pending_doc, +"The number of bytes pending in the memory BIO."); + +static PyObject * +memory_bio_get_eof(PySSLMemoryBIO *self, void *c) +{

+} + +PyDoc_STRVAR(PySSL_memory_bio_eof_doc, +"Whether the memory BIO is at EOF."); + +static PyObject * +memory_bio_read(PySSLMemoryBIO *self, PyObject *args) +{

+

+

+

+

+

+} + +PyDoc_STRVAR(PySSL_memory_bio_read_doc, +"read([len]) -> bytes\n[](#l5.657) +\n[](#l5.658) +Read up to len bytes from the memory BIO.\n[](#l5.659) +\n[](#l5.660) +If len is not specified, read the entire buffer.\n[](#l5.661) +If the return value is an empty bytes instance, this means either\n[](#l5.662) +EOF or that no data is available. Use the "eof" property to\n[](#l5.663) +distinguish between the two."); + +static PyObject * +memory_bio_write(PySSLMemoryBIO *self, PyObject *args) +{

+

+

+

+

+

+ +error:

+} + +PyDoc_STRVAR(PySSL_memory_bio_write_doc, +"write(b) -> len\n[](#l5.702) +\n[](#l5.703) +Writes the bytes b into the memory BIO. Returns the number\n[](#l5.704) +of bytes written."); + +static PyObject * +memory_bio_write_eof(PySSLMemoryBIO *self, PyObject *args) +{

+

+} + +PyDoc_STRVAR(PySSL_memory_bio_write_eof_doc, +"write_eof()\n[](#l5.720) +\n[](#l5.721) +Write an EOF marker to the memory BIO.\n[](#l5.722) +When all data has been read, the "eof" property will be True."); + +static PyGetSetDef memory_bio_getsetlist[] = {

+}; + +static struct PyMethodDef memory_bio_methods[] = {

+}; + +static PyTypeObject PySSLMemoryBIO_Type = {

+}; + #ifdef HAVE_OPENSSL_RAND @@ -3927,6 +4302,8 @@ PyInit__ssl(void) return NULL; if (PyType_Ready(&PySSLSocket_Type) < 0) return NULL;

m = PyModule_Create(&_sslmodule); if (m == NULL) @@ -3990,6 +4367,9 @@ PyInit__ssl(void) if (PyDict_SetItemString(d, "_SSLSocket", (PyObject *)&PySSLSocket_Type) != 0) return NULL;