diff options
author | Jeremy Hylton <jeremy@alum.mit.edu> | 2002-07-02 19:05:33 +0000 |
---|---|---|
committer | Jeremy Hylton <jeremy@alum.mit.edu> | 2002-07-02 19:05:33 +0000 |
commit | ac5b5fc55c30efe11312cf3917a80a66d0feab8b (patch) | |
tree | d845185d82426720cc5cb08fba0c9b38731a12e0 | |
parent | 00d3f2cb8dbc9bba6994ff69c7344978fd56c3bd (diff) | |
download | cpython-ac5b5fc55c30efe11312cf3917a80a66d0feab8b.tar.gz |
Backport variety of SSL fixes accumulated on the trunk.
-rw-r--r-- | Modules/socketmodule.c | 490 |
1 files changed, 348 insertions, 142 deletions
diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c index 733e5e7032..4e1d258987 100644 --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -189,12 +189,38 @@ Socket methods: #endif #ifdef USE_SSL +enum py_ssl_error { + /* these mirror ssl.h */ + PY_SSL_ERROR_NONE, + PY_SSL_ERROR_SSL, + PY_SSL_ERROR_WANT_READ, + PY_SSL_ERROR_WANT_WRITE, + PY_SSL_ERROR_WANT_X509_LOOKUP, + PY_SSL_ERROR_SYSCALL, /* look at error stack/return value/errno */ + PY_SSL_ERROR_ZERO_RETURN, + PY_SSL_ERROR_WANT_CONNECT, + /* start of non ssl.h errorcodes */ + PY_SSL_ERROR_EOF, /* special case of SSL_ERROR_SYSCALL */ + PY_SSL_ERROR_INVALID_ERROR_CODE +}; + #include "openssl/rsa.h" #include "openssl/crypto.h" #include "openssl/x509.h" #include "openssl/pem.h" #include "openssl/ssl.h" #include "openssl/err.h" + +#define X509_NAME_MAXLEN 256 + +/* RAND_* APIs got added to OpenSSL in 0.9.5 */ +#if OPENSSL_VERSION_NUMBER >= 0x0090500fL +# define HAVE_OPENSSL_RAND 1 +#include "openssl/rand.h" +#else +# undef HAVE_OPENSSL_RAND +#endif + #endif /* USE_SSL */ #if defined(MS_WINDOWS) || defined(__BEOS__) @@ -233,7 +259,7 @@ typedef int SOCKET_T; static PyObject *PySocket_Error; #ifdef USE_SSL -static PyObject *SSLErrorObject; +static PyObject *PySSLErrorObject; #endif /* USE_SSL */ @@ -397,21 +423,20 @@ typedef struct { typedef struct { PyObject_HEAD PySocketSockObject *Socket; /* Socket on which we're layered */ - PyObject *x_attr; /* Attributes dictionary */ SSL_CTX* ctx; SSL* ssl; X509* server_cert; BIO* sbio; - char server[256]; - char issuer[256]; + char server[X509_NAME_MAXLEN]; + char issuer[X509_NAME_MAXLEN]; -} SSLObject; +} PySSLObject; -staticforward PyTypeObject SSL_Type; -staticforward PyObject *SSL_SSLwrite(SSLObject *self, PyObject *args); -staticforward PyObject *SSL_SSLread(SSLObject *self, PyObject *args); +staticforward PyTypeObject PySSL_Type; +staticforward PyObject *PySSL_SSLwrite(PySSLObject *self, PyObject *args); +staticforward PyObject *PySSL_SSLread(PySSLObject *self, PyObject *args); -#define SSLObject_Check(v) ((v)->ob_type == &SSL_Type) +#define PySSLObject_Check(v) ((v)->ob_type == &PySSL_Type) #endif /* USE_SSL */ @@ -2162,59 +2187,142 @@ PySocket_inet_ntoa(PyObject *self, PyObject *args) #ifdef USE_SSL -/* This is a C function to be called for new object initialization */ -static SSLObject * -newSSLObject(PySocketSockObject *Sock, char *key_file, char *cert_file) +/* XXX It might be helpful to augment the error message generated + below with the name of the SSL function that generated the error. + I expect it's obvious most of the time. +*/ + +static PyObject * +PySSL_SetError(PySSLObject *obj, int ret) +{ + PyObject *v, *n, *s; + char *errstr; + int err; + enum py_ssl_error p; + + assert(ret <= 0); + + err = SSL_get_error(obj->ssl, ret); + + switch (err) { + case SSL_ERROR_ZERO_RETURN: + errstr = "TLS/SSL connection has been closed"; + p = PY_SSL_ERROR_ZERO_RETURN; + break; + case SSL_ERROR_WANT_READ: + errstr = "The operation did not complete (read)"; + p = PY_SSL_ERROR_WANT_READ; + break; + case SSL_ERROR_WANT_WRITE: + p = PY_SSL_ERROR_WANT_WRITE; + errstr = "The operation did not complete (write)"; + break; + case SSL_ERROR_WANT_X509_LOOKUP: + p = PY_SSL_ERROR_WANT_X509_LOOKUP; + errstr = "The operation did not complete (X509 lookup)"; + break; + case SSL_ERROR_WANT_CONNECT: + p = PY_SSL_ERROR_WANT_CONNECT; + errstr = "The operation did not complete (connect)"; + break; + case SSL_ERROR_SYSCALL: + { + unsigned long e = ERR_get_error(); + if (e == 0) { + if (ret == 0) { + p = PY_SSL_ERROR_EOF; + errstr = "EOF occurred in violation of protocol"; + } else if (ret == -1) { + /* the underlying BIO reported an I/O error */ + return PySocket_Err(); + } else { /* possible? */ + p = PY_SSL_ERROR_SYSCALL; + errstr = "Some I/O error occurred"; + } + } else { + p = PY_SSL_ERROR_SYSCALL; + /* XXX Protected by global interpreter lock */ + errstr = ERR_error_string(e, NULL); + } + break; + } + case SSL_ERROR_SSL: + { + unsigned long e = ERR_get_error(); + p = PY_SSL_ERROR_SSL; + if (e != 0) + /* XXX Protected by global interpreter lock */ + errstr = ERR_error_string(e, NULL); + else { /* possible? */ + errstr = "A failure in the SSL library occurred"; + } + break; + } + default: + p = PY_SSL_ERROR_INVALID_ERROR_CODE; + errstr = "Invalid error code"; + } + n = PyInt_FromLong((long) p); + if (n == NULL) + return NULL; + v = PyTuple_New(2); + if (v == NULL) { + Py_DECREF(n); + return NULL; + } + + s = PyString_FromString(errstr); + if (s == NULL) { + Py_DECREF(v); + Py_DECREF(n); + } + PyTuple_SET_ITEM(v, 0, n); + PyTuple_SET_ITEM(v, 1, s); + PyErr_SetObject(PySSLErrorObject, v); + return NULL; +} + +static PySSLObject * +newPySSLObject(PySocketSockObject *Sock, char *key_file, char *cert_file) { - SSLObject *self; + PySSLObject *self; + char *errstr = NULL; + int ret; - self = PyObject_New(SSLObject, &SSL_Type); /* Create new object */ + self = PyObject_New(PySSLObject, &PySSL_Type); /* Create new object */ if (self == NULL){ - PyErr_SetObject(SSLErrorObject, - PyString_FromString("newSSLObject error")); - return NULL; + errstr = "newPySSLObject error"; + goto fail; + } + memset(self->server, '\0', sizeof(char) * X509_NAME_MAXLEN); + memset(self->issuer, '\0', sizeof(char) * X509_NAME_MAXLEN); + self->server_cert = NULL; + self->ssl = NULL; + self->ctx = NULL; + self->Socket = NULL; + + if ((key_file && !cert_file) || (!key_file && cert_file)) { + errstr = "Both the key & certificate files must be specified"; + goto fail; } - memset(self->server, '\0', sizeof(char) * 256); - memset(self->issuer, '\0', sizeof(char) * 256); - self->x_attr = PyDict_New(); self->ctx = SSL_CTX_new(SSLv23_method()); /* Set up context */ if (self->ctx == NULL) { - PyErr_SetObject(SSLErrorObject, - PyString_FromString("SSL_CTX_new error")); - PyObject_Del(self); - return NULL; + errstr = "SSL_CTX_new error"; + goto fail; } - if ( (key_file && !cert_file) || (!key_file && cert_file) ) - { - PyErr_SetObject(SSLErrorObject, - PyString_FromString( - "Both the key & certificate files must be specified")); - PyObject_Del(self); - return NULL; - } - - if (key_file && cert_file) - { + if (key_file) { if (SSL_CTX_use_PrivateKey_file(self->ctx, key_file, - SSL_FILETYPE_PEM) < 1) - { - PyErr_SetObject(SSLErrorObject, - PyString_FromString( - "SSL_CTX_use_PrivateKey_file error")); - PyObject_Del(self); - return NULL; + SSL_FILETYPE_PEM) < 1) { + errstr = "SSL_CTX_use_PrivateKey_file error"; + goto fail; } if (SSL_CTX_use_certificate_chain_file(self->ctx, - cert_file) < 1) - { - PyErr_SetObject(SSLErrorObject, - PyString_FromString( - "SSL_CTX_use_certificate_chain_file error")); - PyObject_Del(self); - return NULL; + cert_file) < 1) { + errstr = "SSL_CTX_use_certificate_chain_file error"; + goto fail; } } @@ -2224,99 +2332,160 @@ newSSLObject(PySocketSockObject *Sock, char *key_file, char *cert_file) SSL_set_fd(self->ssl, Sock->sock_fd); /* Set the socket for SSL */ SSL_set_connect_state(self->ssl); - if ((SSL_connect(self->ssl)) == -1) { - /* Actually negotiate SSL connection */ - PyErr_SetObject(SSLErrorObject, - PyString_FromString("SSL_connect error")); - PyObject_Del(self); - return NULL; + /* Actually negotiate SSL connection */ + /* XXX If SSL_connect() returns 0, it's also a failure. */ + ret = SSL_connect(self->ssl); + if (ret <= 0) { + PySSL_SetError(self, ret); + goto fail; } self->ssl->debug = 1; if ((self->server_cert = SSL_get_peer_certificate(self->ssl))) { X509_NAME_oneline(X509_get_subject_name(self->server_cert), - self->server, 256); + self->server, X509_NAME_MAXLEN); X509_NAME_oneline(X509_get_issuer_name(self->server_cert), - self->issuer, 256); + self->issuer, X509_NAME_MAXLEN); } - self->x_attr = NULL; self->Socket = Sock; Py_INCREF(self->Socket); return self; + fail: + if (errstr) + PyErr_SetString(PySSLErrorObject, errstr); + Py_DECREF(self); + return NULL; } -/* This is the Python function called for new object initialization */ + static PyObject * PySocket_ssl(PyObject *self, PyObject *args) { - SSLObject *rv; + PySSLObject *rv; PySocketSockObject *Sock; - char *key_file; - char *cert_file; + char *key_file = NULL; + char *cert_file = NULL; - if (!PyArg_ParseTuple(args, "O!zz:ssl", - &PySocketSock_Type, (PyObject*)&Sock, - &key_file, &cert_file) ) + if (!PyArg_ParseTuple(args, "O!|zz:ssl", &PySocketSock_Type, + (PyObject*)&Sock, &key_file, &cert_file)) return NULL; - rv = newSSLObject(Sock, key_file, cert_file); - if ( rv == NULL ) + rv = newPySSLObject(Sock, key_file, cert_file); + if (rv == NULL) return NULL; return (PyObject *)rv; } -static char ssl_doc[] = -"ssl(socket, keyfile, certfile) -> sslobject"; +static char ssl_doc[] = "ssl(socket, [keyfile, certfile]) -> sslobject"; + +/* SSL object methods */ static PyObject * -SSL_server(SSLObject *self, PyObject *args) +PySSL_server(PySSLObject *self, PyObject *args) { + if (!PyArg_ParseTuple(args, ":server")) + return NULL; return PyString_FromString(self->server); } static PyObject * -SSL_issuer(SSLObject *self, PyObject *args) +PySSL_issuer(PySSLObject *self, PyObject *args) { + if (!PyArg_ParseTuple(args, ":issuer")) + return NULL; return PyString_FromString(self->issuer); } -/* SSL object methods */ - -static PyMethodDef SSLMethods[] = { - { "write", (PyCFunction)SSL_SSLwrite, 1 }, - { "read", (PyCFunction)SSL_SSLread, 1 }, - { "server", (PyCFunction)SSL_server, 1 }, - { "issuer", (PyCFunction)SSL_issuer, 1 }, - { NULL, NULL} -}; - -static void SSL_dealloc(SSLObject *self) +static void PySSL_dealloc(PySSLObject *self) { if (self->server_cert) /* Possible not to have one? */ X509_free (self->server_cert); - SSL_CTX_free(self->ctx); - SSL_free(self->ssl); - Py_XDECREF(self->x_attr); + if (self->ssl) + SSL_free(self->ssl); + if (self->ctx) + SSL_CTX_free(self->ctx); Py_XDECREF(self->Socket); PyObject_Del(self); } -static PyObject *SSL_getattr(SSLObject *self, char *name) +static PyObject *PySSL_SSLwrite(PySSLObject *self, PyObject *args) +{ + char *data; + int len; + + if (!PyArg_ParseTuple(args, "s#:write", &data, &len)) + return NULL; + + Py_BEGIN_ALLOW_THREADS + len = SSL_write(self->ssl, data, len); + Py_END_ALLOW_THREADS + if (len > 0) + return PyInt_FromLong(len); + else + return PySSL_SetError(self, len); +} + +static char PySSL_SSLwrite_doc[] = \ +"write(s) -> len\n\ +\n\ +Writes the string s into the SSL object. Returns the number\n\ +of bytes written."; + +static PyObject *PySSL_SSLread(PySSLObject *self, PyObject *args) +{ + PyObject *buf; + int count = 0; + int len = 1024; + + if (!PyArg_ParseTuple(args, "|i:read", &len)) + return NULL; + + if (!(buf = PyString_FromStringAndSize((char *) 0, len))) + return NULL; + + Py_BEGIN_ALLOW_THREADS + count = SSL_read(self->ssl, PyString_AsString(buf), len); + Py_END_ALLOW_THREADS + if (count <= 0) { + Py_DECREF(buf); + return PySSL_SetError(self, count); + } + if (count != len) + _PyString_Resize(&buf, count); + return buf; +} + +static char PySSL_SSLread_doc[] = \ +"read([len]) -> string\n\ +\n\ +Read up to len bytes from the SSL socket."; + +static PyMethodDef PySSLMethods[] = { + {"write", (PyCFunction)PySSL_SSLwrite, METH_VARARGS, + PySSL_SSLwrite_doc}, + {"read", (PyCFunction)PySSL_SSLread, METH_VARARGS, + PySSL_SSLread_doc}, + {"server", (PyCFunction)PySSL_server, METH_VARARGS}, + {"issuer", (PyCFunction)PySSL_issuer, METH_VARARGS}, + {NULL, NULL} +}; + +static PyObject *PySSL_getattr(PySSLObject *self, char *name) { - return Py_FindMethod(SSLMethods, (PyObject *)self, name); + return Py_FindMethod(PySSLMethods, (PyObject *)self, name); } -staticforward PyTypeObject SSL_Type = { +staticforward PyTypeObject PySSL_Type = { PyObject_HEAD_INIT(NULL) 0, /*ob_size*/ - "SSL", /*tp_name*/ - sizeof(SSLObject), /*tp_basicsize*/ + "socket.SSL", /*tp_name*/ + sizeof(PySSLObject), /*tp_basicsize*/ 0, /*tp_itemsize*/ /* methods */ - (destructor)SSL_dealloc, /*tp_dealloc*/ + (destructor)PySSL_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ - (getattrfunc)SSL_getattr, /*tp_getattr*/ + (getattrfunc)PySSL_getattr, /*tp_getattr*/ 0, /*tp_setattr*/ 0, /*tp_compare*/ 0, /*tp_repr*/ @@ -2326,62 +2495,69 @@ staticforward PyTypeObject SSL_Type = { 0, /*tp_hash*/ }; +#ifdef HAVE_OPENSSL_RAND - -static PyObject *SSL_SSLwrite(SSLObject *self, PyObject *args) +/* helper routines for seeding the SSL PRNG */ +static PyObject * +PySSL_RAND_add(PyObject *self, PyObject *args) { - char *data; - size_t len; + char *buf; + int len; + double entropy; - if (!PyArg_ParseTuple(args, "s#:write", &data, &len)) - return NULL; - - Py_BEGIN_ALLOW_THREADS - len = SSL_write(self->ssl, data, len); - Py_END_ALLOW_THREADS - return PyInt_FromLong((long)len); + if (!PyArg_ParseTuple(args, "s#d:RAND_add", &buf, &len, &entropy)) + return NULL; + RAND_add(buf, len, entropy); + Py_INCREF(Py_None); + return Py_None; } -static PyObject *SSL_SSLread(SSLObject *self, PyObject *args) -{ - PyObject *buf; - int count = 0; - int len = 1024; - int res; +static char PySSL_RAND_add_doc[] = \ +"RAND_add(string, entropy)\n\ +\n\ +Mix string into the OpenSSL PRNG state. entropy (a float) is a lower\n\ +bound on the entropy contained in string."; - PyArg_ParseTuple(args, "|i:read", &len); +static PyObject * +PySSL_RAND_status(PyObject *self) +{ + return PyInt_FromLong(RAND_status()); +} - if (!(buf = PyString_FromStringAndSize((char *) 0, len))) - return NULL; /* Error object should already be set */ +static char PySSL_RAND_status_doc[] = \ +"RAND_status() -> 0 or 1\n\ +\n\ +Returns 1 if the OpenSSL PRNG has been seeded with enough data and 0 if not.\n\ +It is necessary to seed the PRNG with RAND_add() on some platforms before\n\ +using the ssl() function."; - Py_BEGIN_ALLOW_THREADS - count = SSL_read(self->ssl, PyString_AsString(buf), len); - Py_END_ALLOW_THREADS - res = SSL_get_error(self->ssl, count); +static PyObject * +PySSL_RAND_egd(PyObject *self, PyObject *args) +{ + int bytes; + char *s; - switch (res) { - case SSL_ERROR_NONE: - assert(count > 0); - break; - case SSL_ERROR_ZERO_RETURN: /* normal EOF */ - assert(count == 0); - break; - default: - Py_DECREF(buf); - return PyErr_SetFromErrno(SSLErrorObject); - } + if (!PyArg_ParseTuple(args, "s:RAND_egd", &s)) + return NULL; - fflush(stderr); + bytes = RAND_egd(s); + if (bytes == -1) { + PyErr_SetString(PySSLErrorObject, + "EGD connection failed or EGD did not return " + "enough data to seed the PRNG"); + return NULL; + } + return PyInt_FromLong(bytes); +} - if (count < 0) { - Py_DECREF(buf); - return PyErr_SetFromErrno(SSLErrorObject); - } +static char PySSL_RAND_egd_doc[] = \ +"RAND_egd(path) -> bytes\n\ +\n\ +Queries the entropy gather daemon (EGD) on socket path. Returns number\n\ +of bytes read. Raises socket.sslerror if connection to EGD fails or\n\ +if it does provide enough data to seed PRNG."; - if (count != len && _PyString_Resize(&buf, count) < 0) - return NULL; - return buf; -} +#endif #endif /* USE_SSL */ @@ -2422,6 +2598,14 @@ static PyMethodDef PySocket_methods[] = { #ifdef USE_SSL {"ssl", PySocket_ssl, METH_VARARGS, ssl_doc}, +#ifdef HAVE_OPENSSL_RAND + {"RAND_add", PySSL_RAND_add, METH_VARARGS, + PySSL_RAND_add_doc}, + {"RAND_egd", PySSL_RAND_egd, METH_VARARGS, + PySSL_RAND_egd_doc}, + {"RAND_status", (PyCFunction)PySSL_RAND_status, METH_VARARGS, + PySSL_RAND_status_doc}, +#endif #endif /* USE_SSL */ {NULL, NULL} /* Sentinel */ }; @@ -2582,7 +2766,7 @@ init_socket(void) #endif /* MS_WINDOWS */ #endif /* RISCOS */ #ifdef USE_SSL - SSL_Type.ob_type = &PyType_Type; + PySSL_Type.ob_type = &PyType_Type; #endif m = Py_InitModule3("_socket", PySocket_methods, module_doc); d = PyModule_GetDict(m); @@ -2592,13 +2776,13 @@ init_socket(void) #ifdef USE_SSL SSL_load_error_strings(); SSLeay_add_ssl_algorithms(); - SSLErrorObject = PyErr_NewException("socket.sslerror", NULL, NULL); - if (SSLErrorObject == NULL) + PySSLErrorObject = PyErr_NewException("socket.sslerror", NULL, NULL); + if (PySSLErrorObject == NULL) return; - PyDict_SetItemString(d, "sslerror", SSLErrorObject); - Py_INCREF(&SSL_Type); + PyDict_SetItemString(d, "sslerror", PySSLErrorObject); + Py_INCREF(&PySSL_Type); if (PyDict_SetItemString(d, "SSLType", - (PyObject *)&SSL_Type) != 0) + (PyObject *)&PySSL_Type) != 0) return; #endif /* USE_SSL */ PyDict_SetItemString(d, "error", PySocket_Error); @@ -2969,6 +3153,28 @@ init_socket(void) insint(d, "IPX_TYPE", IPX_TYPE); #endif +#ifdef USE_SSL + PyModule_AddIntConstant(m, "SSL_ERROR_ZERO_RETURN", + PY_SSL_ERROR_ZERO_RETURN); + PyModule_AddIntConstant(m, "SSL_ERROR_WANT_READ", + PY_SSL_ERROR_WANT_READ); + PyModule_AddIntConstant(m, "SSL_ERROR_WANT_WRITE", + PY_SSL_ERROR_WANT_WRITE); + PyModule_AddIntConstant(m, "SSL_ERROR_WANT_X509_LOOKUP", + PY_SSL_ERROR_WANT_X509_LOOKUP); + PyModule_AddIntConstant(m, "SSL_ERROR_SYSCALL", + PY_SSL_ERROR_SYSCALL); + PyModule_AddIntConstant(m, "SSL_ERROR_SSL", + PY_SSL_ERROR_SSL); + PyModule_AddIntConstant(m, "SSL_ERROR_WANT_CONNECT", + PY_SSL_ERROR_WANT_CONNECT); + /* non ssl.h errorcodes */ + PyModule_AddIntConstant(m, "SSL_ERROR_EOF", + PY_SSL_ERROR_EOF); + PyModule_AddIntConstant(m, "SSL_ERROR_INVALID_ERROR_CODE", + PY_SSL_ERROR_INVALID_ERROR_CODE); +#endif + /* Initialize gethostbyname lock */ #ifdef USE_GETHOSTBYNAME_LOCK gethostbyname_lock = PyThread_allocate_lock(); |