Commit 41032a69 authored by Antoine Pitrou's avatar Antoine Pitrou

Issue #11183: Add finer-grained exceptions to the ssl module, so that

you don't have to inspect the exception's attributes in the common case.
parent b5cab85d
...@@ -59,6 +59,48 @@ Functions, Constants, and Exceptions ...@@ -59,6 +59,48 @@ Functions, Constants, and Exceptions
.. versionchanged:: 3.3 .. versionchanged:: 3.3
:exc:`SSLError` used to be a subtype of :exc:`socket.error`. :exc:`SSLError` used to be a subtype of :exc:`socket.error`.
.. exception:: SSLZeroReturnError
A subclass of :exc:`SSLError` raised when trying to read or write and
the SSL connection has been closed cleanly. Note that this doesn't
mean that the underlying transport (read TCP) has been closed.
.. versionadded:: 3.3
.. exception:: SSLWantReadError
A subclass of :exc:`SSLError` raised by a :ref:`non-blocking SSL socket
<ssl-nonblocking>` when trying to read or write data, but more data needs
to be received on the underlying TCP transport before the request can be
fulfilled.
.. versionadded:: 3.3
.. exception:: SSLWantWriteError
A subclass of :exc:`SSLError` raised by a :ref:`non-blocking SSL socket
<ssl-nonblocking>` when trying to read or write data, but more data needs
to be sent on the underlying TCP transport before the request can be
fulfilled.
.. versionadded:: 3.3
.. exception:: SSLSyscallError
A subclass of :exc:`SSLError` raised when a system error was encountered
while trying to fulfill an operation on a SSL socket. Unfortunately,
there is no easy way to inspect the original errno number.
.. versionadded:: 3.3
.. exception:: SSLEOFError
A subclass of :exc:`SSLError` raised when the SSL connection has been
terminated abrupted. Generally, you shouldn't try to reuse the underlying
transport when this error is encountered.
.. versionadded:: 3.3
.. exception:: CertificateError .. exception:: CertificateError
Raised to signal an error with a certificate (such as mismatching Raised to signal an error with a certificate (such as mismatching
......
...@@ -60,7 +60,11 @@ import re ...@@ -60,7 +60,11 @@ import re
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, SSLError from _ssl import _SSLContext
from _ssl import (
SSLError, SSLZeroReturnError, SSLWantReadError, SSLWantWriteError,
SSLSyscallError, SSLEOFError,
)
from _ssl import CERT_NONE, CERT_OPTIONAL, CERT_REQUIRED from _ssl import CERT_NONE, CERT_OPTIONAL, CERT_REQUIRED
from _ssl import OP_ALL, OP_NO_SSLv2, OP_NO_SSLv3, OP_NO_TLSv1 from _ssl import OP_ALL, OP_NO_SSLv2, OP_NO_SSLv3, OP_NO_TLSv1
from _ssl import RAND_status, RAND_egd, RAND_add, RAND_bytes, RAND_pseudo_bytes from _ssl import RAND_status, RAND_egd, RAND_add, RAND_bytes, RAND_pseudo_bytes
......
...@@ -619,13 +619,10 @@ class NetworkedTests(unittest.TestCase): ...@@ -619,13 +619,10 @@ class NetworkedTests(unittest.TestCase):
try: try:
s.do_handshake() s.do_handshake()
break break
except ssl.SSLError as err: except ssl.SSLWantReadError:
if err.args[0] == ssl.SSL_ERROR_WANT_READ: select.select([s], [], [], 5.0)
select.select([s], [], [], 5.0) except ssl.SSLWantWriteError:
elif err.args[0] == ssl.SSL_ERROR_WANT_WRITE: select.select([], [s], [], 5.0)
select.select([], [s], [], 5.0)
else:
raise
# SSL established # SSL established
self.assertTrue(s.getpeercert()) self.assertTrue(s.getpeercert())
finally: finally:
...@@ -745,13 +742,10 @@ class NetworkedTests(unittest.TestCase): ...@@ -745,13 +742,10 @@ class NetworkedTests(unittest.TestCase):
count += 1 count += 1
s.do_handshake() s.do_handshake()
break break
except ssl.SSLError as err: except ssl.SSLWantReadError:
if err.args[0] == ssl.SSL_ERROR_WANT_READ: select.select([s], [], [])
select.select([s], [], []) except ssl.SSLWantWriteError:
elif err.args[0] == ssl.SSL_ERROR_WANT_WRITE: select.select([], [s], [])
select.select([], [s], [])
else:
raise
s.close() s.close()
if support.verbose: if support.verbose:
sys.stdout.write("\nNeeded %d calls to do_handshake() to establish session.\n" % count) sys.stdout.write("\nNeeded %d calls to do_handshake() to establish session.\n" % count)
...@@ -1030,12 +1024,11 @@ else: ...@@ -1030,12 +1024,11 @@ else:
def _do_ssl_handshake(self): def _do_ssl_handshake(self):
try: try:
self.socket.do_handshake() self.socket.do_handshake()
except ssl.SSLError as err: except (ssl.SSLWantReadError, ssl.SSLWantWriteError):
if err.args[0] in (ssl.SSL_ERROR_WANT_READ, return
ssl.SSL_ERROR_WANT_WRITE): except ssl.SSLEOFError:
return return self.handle_close()
elif err.args[0] == ssl.SSL_ERROR_EOF: except ssl.SSLError:
return self.handle_close()
raise raise
except socket.error as err: except socket.error as err:
if err.args[0] == errno.ECONNABORTED: if err.args[0] == errno.ECONNABORTED:
......
...@@ -341,6 +341,9 @@ Core and Builtins ...@@ -341,6 +341,9 @@ Core and Builtins
Library Library
------- -------
- Issue #11183: Add finer-grained exceptions to the ssl module, so that
you don't have to inspect the exception's attributes in the common case.
- Issue #13216: Add cp65001 codec, the Windows UTF-8 (CP_UTF8). - Issue #13216: Add cp65001 codec, the Windows UTF-8 (CP_UTF8).
- Issue #13226: Add RTLD_xxx constants to the os module. These constants can be - Issue #13226: Add RTLD_xxx constants to the os module. These constants can be
......
...@@ -99,6 +99,11 @@ static PySocketModule_APIObject PySocketModule; ...@@ -99,6 +99,11 @@ static PySocketModule_APIObject PySocketModule;
/* SSL error object */ /* SSL error object */
static PyObject *PySSLErrorObject; static PyObject *PySSLErrorObject;
static PyObject *PySSLZeroReturnErrorObject;
static PyObject *PySSLWantReadErrorObject;
static PyObject *PySSLWantWriteErrorObject;
static PyObject *PySSLSyscallErrorObject;
static PyObject *PySSLEOFErrorObject;
#ifdef WITH_THREAD #ifdef WITH_THREAD
...@@ -191,6 +196,7 @@ static PyObject * ...@@ -191,6 +196,7 @@ static PyObject *
PySSL_SetError(PySSLSocket *obj, int ret, char *filename, int lineno) PySSL_SetError(PySSLSocket *obj, int ret, char *filename, int lineno)
{ {
PyObject *v; PyObject *v;
PyObject *type = PySSLErrorObject;
char buf[2048]; char buf[2048];
char *errstr; char *errstr;
int err; int err;
...@@ -203,15 +209,18 @@ PySSL_SetError(PySSLSocket *obj, int ret, char *filename, int lineno) ...@@ -203,15 +209,18 @@ PySSL_SetError(PySSLSocket *obj, int ret, char *filename, int lineno)
switch (err) { switch (err) {
case SSL_ERROR_ZERO_RETURN: case SSL_ERROR_ZERO_RETURN:
errstr = "TLS/SSL connection has been closed"; errstr = "TLS/SSL connection has been closed (EOF)";
type = PySSLZeroReturnErrorObject;
p = PY_SSL_ERROR_ZERO_RETURN; p = PY_SSL_ERROR_ZERO_RETURN;
break; break;
case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_READ:
errstr = "The operation did not complete (read)"; errstr = "The operation did not complete (read)";
type = PySSLWantReadErrorObject;
p = PY_SSL_ERROR_WANT_READ; p = PY_SSL_ERROR_WANT_READ;
break; break;
case SSL_ERROR_WANT_WRITE: case SSL_ERROR_WANT_WRITE:
p = PY_SSL_ERROR_WANT_WRITE; p = PY_SSL_ERROR_WANT_WRITE;
type = PySSLWantWriteErrorObject;
errstr = "The operation did not complete (write)"; errstr = "The operation did not complete (write)";
break; break;
case SSL_ERROR_WANT_X509_LOOKUP: case SSL_ERROR_WANT_X509_LOOKUP:
...@@ -230,6 +239,7 @@ PySSL_SetError(PySSLSocket *obj, int ret, char *filename, int lineno) ...@@ -230,6 +239,7 @@ PySSL_SetError(PySSLSocket *obj, int ret, char *filename, int lineno)
= (PySocketSockObject *) PyWeakref_GetObject(obj->Socket); = (PySocketSockObject *) PyWeakref_GetObject(obj->Socket);
if (ret == 0 || (((PyObject *)s) == Py_None)) { if (ret == 0 || (((PyObject *)s) == Py_None)) {
p = PY_SSL_ERROR_EOF; p = PY_SSL_ERROR_EOF;
type = PySSLEOFErrorObject;
errstr = "EOF occurred in violation of protocol"; errstr = "EOF occurred in violation of protocol";
} else if (ret == -1) { } else if (ret == -1) {
/* underlying BIO reported an I/O error */ /* underlying BIO reported an I/O error */
...@@ -240,6 +250,7 @@ PySSL_SetError(PySSLSocket *obj, int ret, char *filename, int lineno) ...@@ -240,6 +250,7 @@ PySSL_SetError(PySSLSocket *obj, int ret, char *filename, int lineno)
return v; return v;
} else { /* possible? */ } else { /* possible? */
p = PY_SSL_ERROR_SYSCALL; p = PY_SSL_ERROR_SYSCALL;
type = PySSLSyscallErrorObject;
errstr = "Some I/O error occurred"; errstr = "Some I/O error occurred";
} }
} else { } else {
...@@ -272,7 +283,7 @@ PySSL_SetError(PySSLSocket *obj, int ret, char *filename, int lineno) ...@@ -272,7 +283,7 @@ PySSL_SetError(PySSLSocket *obj, int ret, char *filename, int lineno)
ERR_clear_error(); ERR_clear_error();
v = Py_BuildValue("(is)", p, buf); v = Py_BuildValue("(is)", p, buf);
if (v != NULL) { if (v != NULL) {
PyErr_SetObject(PySSLErrorObject, v); PyErr_SetObject(type, v);
Py_DECREF(v); Py_DECREF(v);
} }
return NULL; return NULL;
...@@ -2300,6 +2311,23 @@ parse_openssl_version(unsigned long libver, ...@@ -2300,6 +2311,23 @@ parse_openssl_version(unsigned long libver,
PyDoc_STRVAR(SSLError_doc, PyDoc_STRVAR(SSLError_doc,
"An error occurred in the SSL implementation."); "An error occurred in the SSL implementation.");
PyDoc_STRVAR(SSLZeroReturnError_doc,
"SSL/TLS session closed cleanly.");
PyDoc_STRVAR(SSLWantReadError_doc,
"Non-blocking SSL socket needs to read more data\n"
"before the requested operation can be completed.");
PyDoc_STRVAR(SSLWantWriteError_doc,
"Non-blocking SSL socket needs to write more data\n"
"before the requested operation can be completed.");
PyDoc_STRVAR(SSLSyscallError_doc,
"System error when attempting SSL operation.");
PyDoc_STRVAR(SSLEOFError_doc,
"SSL/TLS connection terminated abruptly.");
PyMODINIT_FUNC PyMODINIT_FUNC
PyInit__ssl(void) PyInit__ssl(void)
...@@ -2343,7 +2371,33 @@ PyInit__ssl(void) ...@@ -2343,7 +2371,33 @@ PyInit__ssl(void)
NULL); NULL);
if (PySSLErrorObject == NULL) if (PySSLErrorObject == NULL)
return NULL; return NULL;
if (PyDict_SetItemString(d, "SSLError", PySSLErrorObject) != 0) PySSLZeroReturnErrorObject = PyErr_NewExceptionWithDoc(
"ssl.SSLZeroReturnError", SSLZeroReturnError_doc,
PySSLErrorObject, NULL);
PySSLWantReadErrorObject = PyErr_NewExceptionWithDoc(
"ssl.SSLWantReadError", SSLWantReadError_doc,
PySSLErrorObject, NULL);
PySSLWantWriteErrorObject = PyErr_NewExceptionWithDoc(
"ssl.SSLWantWriteError", SSLWantWriteError_doc,
PySSLErrorObject, NULL);
PySSLSyscallErrorObject = PyErr_NewExceptionWithDoc(
"ssl.SSLSyscallError", SSLSyscallError_doc,
PySSLErrorObject, NULL);
PySSLEOFErrorObject = PyErr_NewExceptionWithDoc(
"ssl.SSLEOFError", SSLEOFError_doc,
PySSLErrorObject, NULL);
if (PySSLZeroReturnErrorObject == NULL
|| PySSLWantReadErrorObject == NULL
|| PySSLWantWriteErrorObject == NULL
|| PySSLSyscallErrorObject == NULL
|| PySSLEOFErrorObject == NULL)
return NULL;
if (PyDict_SetItemString(d, "SSLError", PySSLErrorObject) != 0
|| PyDict_SetItemString(d, "SSLZeroReturnError", PySSLZeroReturnErrorObject) != 0
|| PyDict_SetItemString(d, "SSLWantReadError", PySSLWantReadErrorObject) != 0
|| PyDict_SetItemString(d, "SSLWantWriteError", PySSLWantWriteErrorObject) != 0
|| PyDict_SetItemString(d, "SSLSyscallError", PySSLSyscallErrorObject) != 0
|| PyDict_SetItemString(d, "SSLEOFError", PySSLEOFErrorObject) != 0)
return NULL; return NULL;
if (PyDict_SetItemString(d, "_SSLContext", if (PyDict_SetItemString(d, "_SSLContext",
(PyObject *)&PySSLContext_Type) != 0) (PyObject *)&PySSLContext_Type) != 0)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment