diff options
author | Bill Janssen <janssen@parc.com> | 2007-09-10 21:51:02 (GMT) |
---|---|---|
committer | Bill Janssen <janssen@parc.com> | 2007-09-10 21:51:02 (GMT) |
commit | 98d19dafd9c9d95338887b9e53c77ec6960918e0 (patch) | |
tree | 1a21af6b4c5c2ba186ff8395663bc751fced6a94 /Modules | |
parent | a0c05512ec071b98e8170c8cfe845bee6fc934da (diff) | |
download | cpython-98d19dafd9c9d95338887b9e53c77ec6960918e0.zip cpython-98d19dafd9c9d95338887b9e53c77ec6960918e0.tar.gz cpython-98d19dafd9c9d95338887b9e53c77ec6960918e0.tar.bz2 |
More work on SSL support.
* Much expanded test suite:
All protocols tested against all other protocols.
All protocols tested with all certificate options.
Tests for bad key and bad cert.
Test of STARTTLS functionality.
Test of RAND_* functions.
* Fixes for threading/malloc bug.
* Issue 1065 fixed:
sslsocket class renamed to SSLSocket.
sslerror class renamed to SSLError.
Function "wrap_socket" now used to wrap an existing socket.
* Issue 1583946 finally fixed:
Support for subjectAltName added.
Subject name now returned as proper DN list of RDNs.
* SSLError exported from socket as "sslerror".
* RAND_* functions properly exported from ssl.py.
* Documentation improved:
Example of how to create a self-signed certificate.
Better indexing.
Diffstat (limited to 'Modules')
-rw-r--r-- | Modules/_ssl.c | 803 |
1 files changed, 672 insertions, 131 deletions
diff --git a/Modules/_ssl.c b/Modules/_ssl.c index a34c3c8..45a5490 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -1,14 +1,38 @@ /* SSL socket module SSL support based on patches by Brian E Gallew and Laszlo Kovacs. + Re-worked a bit by Bill Janssen to add server-side support and + certificate decoding. - This module is imported by socket.py. It should *not* be used + This module is imported by ssl.py. It should *not* be used directly. + XXX should partial writes be enabled, SSL_MODE_ENABLE_PARTIAL_WRITE? + + XXX what about SSL_MODE_AUTO_RETRY */ #include "Python.h" +#ifdef WITH_THREAD +#include "pythread.h" +#define PySSL_BEGIN_ALLOW_THREADS { \ + PyThreadState *_save; \ + if (_ssl_locks_count>0) {_save = PyEval_SaveThread();} +#define PySSL_BLOCK_THREADS if (_ssl_locks_count>0){PyEval_RestoreThread(_save)}; +#define PySSL_UNBLOCK_THREADS if (_ssl_locks_count>0){_save = PyEval_SaveThread()}; +#define PySSL_END_ALLOW_THREADS if (_ssl_locks_count>0){PyEval_RestoreThread(_save);} \ + } + +#else /* no WITH_THREAD */ + +#define PySSL_BEGIN_ALLOW_THREADS +#define PySSL_BLOCK_THREADS +#define PySSL_UNBLOCK_THREADS +#define PySSL_END_ALLOW_THREADS + +#endif + enum py_ssl_error { /* these mirror ssl.h */ PY_SSL_ERROR_NONE, @@ -55,6 +79,7 @@ enum py_ssl_version { #include "openssl/rsa.h" #include "openssl/crypto.h" #include "openssl/x509.h" +#include "openssl/x509v3.h" #include "openssl/pem.h" #include "openssl/ssl.h" #include "openssl/err.h" @@ -63,6 +88,15 @@ enum py_ssl_version { /* SSL error object */ static PyObject *PySSLErrorObject; +#ifdef WITH_THREAD + +/* serves as a flag to see whether we've initialized the SSL thread support. */ +/* 0 means no, greater than 0 means yes */ + +static unsigned int _ssl_locks_count = 0; + +#endif /* def WITH_THREAD */ + /* SSL socket object */ #define X509_NAME_MAXLEN 256 @@ -90,8 +124,9 @@ static PyObject *PySSL_SSLwrite(PySSLObject *self, PyObject *args); static PyObject *PySSL_SSLread(PySSLObject *self, PyObject *args); static int check_socket_and_wait_for_timeout(PySocketSockObject *s, int writing); -static PyObject *PySSL_peercert(PySSLObject *self); - +static PyObject *PySSL_peercert(PySSLObject *self, PyObject *args); +static PyObject *PySSL_cipher(PySSLObject *self); +static PyObject *PySSL_SSLshutdown(PySSLObject *self); #define PySSLObject_Check(v) (Py_Type(v) == &PySSL_Type) @@ -126,7 +161,7 @@ PySSL_SetError(PySSLObject *obj, int ret, char *filename, int lineno) assert(ret <= 0); - if ((obj != NULL) && (obj->ssl != NULL)) { + if (obj->ssl != NULL) { err = SSL_get_error(obj->ssl, ret); switch (err) { @@ -202,6 +237,25 @@ PySSL_SetError(PySSLObject *obj, int ret, char *filename, int lineno) return NULL; } +static PyObject * +_setSSLError (char *errstr, int errcode, char *filename, int lineno) { + + char buf[2048]; + PyObject *v; + + if (errstr == NULL) { + errcode = ERR_peek_last_error(); + errstr = ERR_error_string(errcode, NULL); + } + PyOS_snprintf(buf, sizeof(buf), "_ssl.c:%d: %s", lineno, errstr); + v = Py_BuildValue("(is)", errcode, buf); + if (v != NULL) { + PyErr_SetObject(PySSLErrorObject, v); + Py_DECREF(v); + } + return NULL; +} + static PySSLObject * newPySSLObject(PySocketSockObject *Sock, char *key_file, char *cert_file, enum py_ssl_server_or_client socket_type, @@ -226,6 +280,10 @@ newPySSLObject(PySocketSockObject *Sock, char *key_file, char *cert_file, self->ctx = NULL; self->Socket = NULL; + /* Make sure the SSL error state is initialized */ + (void) ERR_get_state(); + ERR_clear_error(); + if ((key_file && !cert_file) || (!key_file && cert_file)) { errstr = ERRSTR("Both the key & certificate files " "must be specified"); @@ -239,16 +297,16 @@ newPySSLObject(PySocketSockObject *Sock, char *key_file, char *cert_file, goto fail; } - Py_BEGIN_ALLOW_THREADS + PySSL_BEGIN_ALLOW_THREADS if (proto_version == PY_SSL_VERSION_TLS1) self->ctx = SSL_CTX_new(TLSv1_method()); /* Set up context */ else if (proto_version == PY_SSL_VERSION_SSL3) self->ctx = SSL_CTX_new(SSLv3_method()); /* Set up context */ else if (proto_version == PY_SSL_VERSION_SSL2) self->ctx = SSL_CTX_new(SSLv2_method()); /* Set up context */ - else + else if (proto_version == PY_SSL_VERSION_SSL23) self->ctx = SSL_CTX_new(SSLv23_method()); /* Set up context */ - Py_END_ALLOW_THREADS + PySSL_END_ALLOW_THREADS if (self->ctx == NULL) { errstr = ERRSTR("Invalid SSL protocol variant specified."); @@ -261,39 +319,46 @@ newPySSLObject(PySocketSockObject *Sock, char *key_file, char *cert_file, "verification of other-side certificates."); goto fail; } else { - Py_BEGIN_ALLOW_THREADS + PySSL_BEGIN_ALLOW_THREADS ret = SSL_CTX_load_verify_locations(self->ctx, cacerts_file, NULL); - Py_END_ALLOW_THREADS + PySSL_END_ALLOW_THREADS if (ret != 1) { - PySSL_SetError(NULL, 0, __FILE__, __LINE__); + _setSSLError(NULL, 0, __FILE__, __LINE__); goto fail; } } } if (key_file) { - Py_BEGIN_ALLOW_THREADS + PySSL_BEGIN_ALLOW_THREADS ret = SSL_CTX_use_PrivateKey_file(self->ctx, key_file, SSL_FILETYPE_PEM); - Py_END_ALLOW_THREADS + PySSL_END_ALLOW_THREADS if (ret != 1) { - PySSL_SetError(NULL, 0, __FILE__, __LINE__); + _setSSLError(NULL, ret, __FILE__, __LINE__); goto fail; } - Py_BEGIN_ALLOW_THREADS + PySSL_BEGIN_ALLOW_THREADS ret = SSL_CTX_use_certificate_chain_file(self->ctx, - cert_file); - Py_END_ALLOW_THREADS + cert_file); + PySSL_END_ALLOW_THREADS if (ret != 1) { - PySSL_SetError(NULL, 0, __FILE__, __LINE__); - goto fail; + /* + fprintf(stderr, "ret is %d, errcode is %lu, %lu, with file \"%s\"\n", + ret, ERR_peek_error(), ERR_peek_last_error(), cert_file); + */ + if (ERR_peek_last_error() != 0) { + _setSSLError(NULL, ret, __FILE__, __LINE__); + goto fail; + } } - /* ssl compatibility */ - SSL_CTX_set_options(self->ctx, SSL_OP_ALL); } + /* ssl compatibility */ + SSL_CTX_set_options(self->ctx, SSL_OP_ALL); + verification_mode = SSL_VERIFY_NONE; if (certreq == PY_SSL_CERT_OPTIONAL) verification_mode = SSL_VERIFY_PEER; @@ -303,9 +368,9 @@ newPySSLObject(PySocketSockObject *Sock, char *key_file, char *cert_file, SSL_CTX_set_verify(self->ctx, verification_mode, NULL); /* set verify lvl */ - Py_BEGIN_ALLOW_THREADS + PySSL_BEGIN_ALLOW_THREADS self->ssl = SSL_new(self->ctx); /* New ssl struct */ - Py_END_ALLOW_THREADS + PySSL_END_ALLOW_THREADS SSL_set_fd(self->ssl, Sock->sock_fd); /* Set the socket for SSL */ /* If the socket is in non-blocking mode or timeout mode, set the BIO @@ -317,24 +382,24 @@ newPySSLObject(PySocketSockObject *Sock, char *key_file, char *cert_file, BIO_set_nbio(SSL_get_wbio(self->ssl), 1); } - Py_BEGIN_ALLOW_THREADS + PySSL_BEGIN_ALLOW_THREADS if (socket_type == PY_SSL_CLIENT) SSL_set_connect_state(self->ssl); else SSL_set_accept_state(self->ssl); - Py_END_ALLOW_THREADS + PySSL_END_ALLOW_THREADS /* Actually negotiate SSL connection */ /* XXX If SSL_connect() returns 0, it's also a failure. */ sockstate = 0; do { - Py_BEGIN_ALLOW_THREADS + PySSL_BEGIN_ALLOW_THREADS if (socket_type == PY_SSL_CLIENT) ret = SSL_connect(self->ssl); else ret = SSL_accept(self->ssl); err = SSL_get_error(self->ssl, ret); - Py_END_ALLOW_THREADS + PySSL_END_ALLOW_THREADS if(PyErr_CheckSignals()) { goto fail; } @@ -367,14 +432,14 @@ newPySSLObject(PySocketSockObject *Sock, char *key_file, char *cert_file, } self->ssl->debug = 1; - Py_BEGIN_ALLOW_THREADS + PySSL_BEGIN_ALLOW_THREADS if ((self->peer_cert = SSL_get_peer_certificate(self->ssl))) { X509_NAME_oneline(X509_get_subject_name(self->peer_cert), self->server, X509_NAME_MAXLEN); X509_NAME_oneline(X509_get_issuer_name(self->peer_cert), self->issuer, X509_NAME_MAXLEN); } - Py_END_ALLOW_THREADS + PySSL_END_ALLOW_THREADS self->Socket = Sock; Py_INCREF(self->Socket); return self; @@ -437,96 +502,313 @@ PySSL_issuer(PySSLObject *self) } static PyObject * +_create_tuple_for_attribute (ASN1_OBJECT *name, ASN1_STRING *value) { + + char namebuf[X509_NAME_MAXLEN]; + int buflen; + PyObject *name_obj; + PyObject *value_obj; + PyObject *attr; + unsigned char *valuebuf = NULL; + + buflen = OBJ_obj2txt(namebuf, sizeof(namebuf), name, 0); + if (buflen < 0) { + _setSSLError(NULL, 0, __FILE__, __LINE__); + goto fail; + } + name_obj = PyString_FromStringAndSize(namebuf, buflen); + if (name_obj == NULL) + goto fail; + + buflen = ASN1_STRING_to_UTF8(&valuebuf, value); + if (buflen < 0) { + _setSSLError(NULL, 0, __FILE__, __LINE__); + Py_DECREF(name_obj); + goto fail; + } + value_obj = PyUnicode_DecodeUTF8((char *) valuebuf, + buflen, "strict"); + OPENSSL_free(valuebuf); + if (value_obj == NULL) { + Py_DECREF(name_obj); + goto fail; + } + attr = PyTuple_New(2); + if (attr == NULL) { + Py_DECREF(name_obj); + Py_DECREF(value_obj); + goto fail; + } + PyTuple_SET_ITEM(attr, 0, name_obj); + PyTuple_SET_ITEM(attr, 1, value_obj); + return attr; + + fail: + return NULL; +} + +static PyObject * _create_tuple_for_X509_NAME (X509_NAME *xname) { - PyObject *pt = NULL; - PyObject *entry_tuple = NULL; + PyObject *dn = NULL; /* tuple which represents the "distinguished name" */ + PyObject *rdn = NULL; /* tuple to hold a "relative distinguished name" */ + PyObject *rdnt; + PyObject *attr = NULL; /* tuple to hold an attribute */ int entry_count = X509_NAME_entry_count(xname); + X509_NAME_ENTRY *entry; + ASN1_OBJECT *name; + ASN1_STRING *value; int index_counter; + int rdn_level = -1; + int retcode; - pt = PyTuple_New(entry_count); - if (pt == NULL) + dn = PyList_New(0); + if (dn == NULL) return NULL; + /* now create another tuple to hold the top-level RDN */ + rdn = PyList_New(0); + if (rdn == NULL) + goto fail0; for (index_counter = 0; - index_counter < X509_NAME_entry_count(xname); + index_counter < entry_count; index_counter++) { - char namebuf[X509_NAME_MAXLEN]; - int buflen; - PyObject *name_obj; - ASN1_STRING *value; - PyObject *value_obj; - unsigned char *valuebuf = NULL; - - X509_NAME_ENTRY *entry = X509_NAME_get_entry(xname, - index_counter); - - ASN1_OBJECT *name = X509_NAME_ENTRY_get_object(entry); - buflen = OBJ_obj2txt(namebuf, sizeof(namebuf), name, 0); - if (buflen < 0) - goto fail0; - name_obj = PyString_FromStringAndSize(namebuf, buflen); - if (name_obj == NULL) - goto fail0; + entry = X509_NAME_get_entry(xname, index_counter); + + /* check to see if we've gotten to a new RDN */ + if (rdn_level >= 0) { + if (rdn_level != entry->set) { + /* yes, new RDN */ + /* add old RDN to DN */ + rdnt = PyList_AsTuple(rdn); + Py_DECREF(rdn); + if (rdnt == NULL) + goto fail0; + retcode = PyList_Append(dn, rdnt); + Py_DECREF(rdnt); + if (retcode < 0) + goto fail0; + /* create new RDN */ + rdn = PyList_New(0); + if (rdn == NULL) + goto fail0; + } + } + rdn_level = entry->set; + /* now add this attribute to the current RDN */ + name = X509_NAME_ENTRY_get_object(entry); value = X509_NAME_ENTRY_get_data(entry); - buflen = ASN1_STRING_to_UTF8(&valuebuf, value); - if (buflen < 0) { - Py_DECREF(name_obj); + attr = _create_tuple_for_attribute(name, value); + /* + fprintf(stderr, "RDN level %d, attribute %s: %s\n", + entry->set, + PyString_AS_STRING(PyTuple_GET_ITEM(attr, 0)), + PyString_AS_STRING(PyTuple_GET_ITEM(attr, 1))); + */ + if (attr == NULL) + goto fail1; + retcode = PyList_Append(rdn, attr); + Py_DECREF(attr); + if (retcode < 0) + goto fail1; + } + /* now, there's typically a dangling RDN */ + if ((rdn != NULL) && (PyList_Size(rdn) > 0)) { + rdnt = PyList_AsTuple(rdn); + Py_DECREF(rdn); + if (rdnt == NULL) goto fail0; - } - value_obj = PyUnicode_DecodeUTF8((char *) valuebuf, - buflen, "strict"); - OPENSSL_free(valuebuf); - if (value_obj == NULL) { - Py_DECREF(name_obj); + retcode = PyList_Append(dn, rdnt); + Py_DECREF(rdnt); + if (retcode < 0) goto fail0; + } + + /* convert list to tuple */ + rdnt = PyList_AsTuple(dn); + Py_DECREF(dn); + if (rdnt == NULL) + return NULL; + return rdnt; + + fail1: + Py_XDECREF(rdn); + + fail0: + Py_XDECREF(dn); + return NULL; +} + +static PyObject * +_get_peer_alt_names (X509 *certificate) { + + /* this code follows the procedure outlined in + OpenSSL's crypto/x509v3/v3_prn.c:X509v3_EXT_print() + function to extract the STACK_OF(GENERAL_NAME), + then iterates through the stack to add the + names. */ + + int i, j; + PyObject *peer_alt_names = Py_None; + PyObject *v, *t; + X509_EXTENSION *ext = NULL; + GENERAL_NAMES *names = NULL; + GENERAL_NAME *name; + X509V3_EXT_METHOD *method; + BIO *biobuf = NULL; + char buf[2048]; + char *vptr; + int len; + const unsigned char *p; + + if (certificate == NULL) + return peer_alt_names; + + /* get a memory buffer */ + biobuf = BIO_new(BIO_s_mem()); + + i = 0; + while ((i = X509_get_ext_by_NID( + certificate, NID_subject_alt_name, i)) >= 0) { + + if (peer_alt_names == Py_None) { + peer_alt_names = PyList_New(0); + if (peer_alt_names == NULL) + goto fail; } - entry_tuple = PyTuple_New(2); - if (entry_tuple == NULL) { - Py_DECREF(name_obj); - Py_DECREF(value_obj); - goto fail0; + + /* now decode the altName */ + ext = X509_get_ext(certificate, i); + if(!(method = X509V3_EXT_get(ext))) { + PyErr_SetString(PySSLErrorObject, + ERRSTR("No method for internalizing subjectAltName!")); + goto fail; + } + + p = ext->value->data; + if(method->it) + names = (GENERAL_NAMES*) (ASN1_item_d2i(NULL, + &p, + ext->value->length, + ASN1_ITEM_ptr(method->it))); + else + names = (GENERAL_NAMES*) (method->d2i(NULL, + &p, + ext->value->length)); + + for(j = 0; j < sk_GENERAL_NAME_num(names); j++) { + + /* get a rendering of each name in the set of names */ + + name = sk_GENERAL_NAME_value(names, j); + if (name->type == GEN_DIRNAME) { + + /* we special-case DirName as a tuple of tuples of attributes */ + + t = PyTuple_New(2); + if (t == NULL) { + goto fail; + } + + v = PyString_FromString("DirName"); + if (v == NULL) { + Py_DECREF(t); + goto fail; + } + PyTuple_SET_ITEM(t, 0, v); + + v = _create_tuple_for_X509_NAME (name->d.dirn); + if (v == NULL) { + Py_DECREF(t); + goto fail; + } + PyTuple_SET_ITEM(t, 1, v); + + } else { + + /* for everything else, we use the OpenSSL print form */ + + (void) BIO_reset(biobuf); + GENERAL_NAME_print(biobuf, name); + len = BIO_gets(biobuf, buf, sizeof(buf)-1); + if (len < 0) { + _setSSLError(NULL, 0, __FILE__, __LINE__); + goto fail; + } + vptr = strchr(buf, ':'); + if (vptr == NULL) + goto fail; + t = PyTuple_New(2); + if (t == NULL) + goto fail; + v = PyString_FromStringAndSize(buf, (vptr - buf)); + if (v == NULL) { + Py_DECREF(t); + goto fail; + } + PyTuple_SET_ITEM(t, 0, v); + v = PyString_FromStringAndSize((vptr + 1), (len - (vptr - buf + 1))); + if (v == NULL) { + Py_DECREF(t); + goto fail; + } + PyTuple_SET_ITEM(t, 1, v); + } + + /* and add that rendering to the list */ + + if (PyList_Append(peer_alt_names, t) < 0) { + Py_DECREF(t); + goto fail; + } + Py_DECREF(t); } - PyTuple_SET_ITEM(entry_tuple, 0, name_obj); - PyTuple_SET_ITEM(entry_tuple, 1, value_obj); - PyTuple_SET_ITEM(pt, index_counter, entry_tuple); } - return pt; + BIO_free(biobuf); + if (peer_alt_names != Py_None) { + v = PyList_AsTuple(peer_alt_names); + Py_DECREF(peer_alt_names); + return v; + } else { + return peer_alt_names; + } + + + fail: + if (biobuf != NULL) + BIO_free(biobuf); + + if (peer_alt_names != Py_None) { + Py_XDECREF(peer_alt_names); + } - fail0: - Py_XDECREF(pt); return NULL; } static PyObject * -PySSL_peercert(PySSLObject *self) -{ +_decode_certificate (X509 *certificate, int verbose) { + PyObject *retval = NULL; BIO *biobuf = NULL; PyObject *peer; + PyObject *peer_alt_names = NULL; PyObject *issuer; PyObject *version; + PyObject *sn_obj; + ASN1_INTEGER *serialNumber; char buf[2048]; int len; ASN1_TIME *notBefore, *notAfter; PyObject *pnotBefore, *pnotAfter; - int verification; - - if (!self->peer_cert) - Py_RETURN_NONE; retval = PyDict_New(); if (retval == NULL) return NULL; - verification = SSL_CTX_get_verify_mode(self->ctx); - if ((verification & SSL_VERIFY_PEER) == 0) - return retval; - peer = _create_tuple_for_X509_NAME( - X509_get_subject_name(self->peer_cert)); + X509_get_subject_name(certificate)); if (peer == NULL) goto fail0; if (PyDict_SetItemString(retval, (const char *) "subject", peer) < 0) { @@ -535,51 +817,98 @@ PySSL_peercert(PySSLObject *self) } Py_DECREF(peer); - issuer = _create_tuple_for_X509_NAME( - X509_get_issuer_name(self->peer_cert)); - if (issuer == NULL) - goto fail0; - if (PyDict_SetItemString(retval, (const char *)"issuer", issuer) < 0) { + if (verbose) { + issuer = _create_tuple_for_X509_NAME( + X509_get_issuer_name(certificate)); + if (issuer == NULL) + goto fail0; + if (PyDict_SetItemString(retval, (const char *)"issuer", issuer) < 0) { + Py_DECREF(issuer); + goto fail0; + } Py_DECREF(issuer); - goto fail0; - } - Py_DECREF(issuer); - - version = PyInt_FromLong(X509_get_version(self->peer_cert)); - if (PyDict_SetItemString(retval, "version", version) < 0) { + + version = PyInt_FromLong(X509_get_version(certificate) + 1); + if (PyDict_SetItemString(retval, "version", version) < 0) { + Py_DECREF(version); + goto fail0; + } Py_DECREF(version); - goto fail0; } - Py_DECREF(version); - + /* get a memory buffer */ biobuf = BIO_new(BIO_s_mem()); - - notBefore = X509_get_notBefore(self->peer_cert); - ASN1_TIME_print(biobuf, notBefore); - len = BIO_gets(biobuf, buf, sizeof(buf)-1); - pnotBefore = PyString_FromStringAndSize(buf, len); - if (pnotBefore == NULL) - goto fail1; - if (PyDict_SetItemString(retval, "notBefore", pnotBefore) < 0) { + + if (verbose) { + + (void) BIO_reset(biobuf); + serialNumber = X509_get_serialNumber(certificate); + /* should not exceed 20 octets, 160 bits, so buf is big enough */ + i2a_ASN1_INTEGER(biobuf, serialNumber); + len = BIO_gets(biobuf, buf, sizeof(buf)-1); + if (len < 0) { + _setSSLError(NULL, 0, __FILE__, __LINE__); + goto fail1; + } + sn_obj = PyString_FromStringAndSize(buf, len); + if (sn_obj == NULL) + goto fail1; + if (PyDict_SetItemString(retval, "serialNumber", sn_obj) < 0) { + Py_DECREF(sn_obj); + goto fail1; + } + Py_DECREF(sn_obj); + + (void) BIO_reset(biobuf); + notBefore = X509_get_notBefore(certificate); + ASN1_TIME_print(biobuf, notBefore); + len = BIO_gets(biobuf, buf, sizeof(buf)-1); + if (len < 0) { + _setSSLError(NULL, 0, __FILE__, __LINE__); + goto fail1; + } + pnotBefore = PyString_FromStringAndSize(buf, len); + if (pnotBefore == NULL) + goto fail1; + if (PyDict_SetItemString(retval, "notBefore", pnotBefore) < 0) { + Py_DECREF(pnotBefore); + goto fail1; + } Py_DECREF(pnotBefore); - goto fail1; } - Py_DECREF(pnotBefore); (void) BIO_reset(biobuf); - notAfter = X509_get_notAfter(self->peer_cert); + notAfter = X509_get_notAfter(certificate); ASN1_TIME_print(biobuf, notAfter); len = BIO_gets(biobuf, buf, sizeof(buf)-1); - BIO_free(biobuf); + if (len < 0) { + _setSSLError(NULL, 0, __FILE__, __LINE__); + goto fail1; + } pnotAfter = PyString_FromStringAndSize(buf, len); if (pnotAfter == NULL) - goto fail0; + goto fail1; if (PyDict_SetItemString(retval, "notAfter", pnotAfter) < 0) { Py_DECREF(pnotAfter); - goto fail0; + goto fail1; } Py_DECREF(pnotAfter); + + /* Now look for subjectAltName */ + + peer_alt_names = _get_peer_alt_names(certificate); + if (peer_alt_names == NULL) + goto fail1; + else if (peer_alt_names != Py_None) { + if (PyDict_SetItemString(retval, "subjectAltName", + peer_alt_names) < 0) { + Py_DECREF(peer_alt_names); + goto fail1; + } + Py_DECREF(peer_alt_names); + } + + BIO_free(biobuf); return retval; fail1: @@ -590,6 +919,141 @@ PySSL_peercert(PySSLObject *self) return NULL; } + +static PyObject * +PySSL_test_decode_certificate (PyObject *mod, PyObject *args) { + + PyObject *retval = NULL; + char *filename = NULL; + X509 *x=NULL; + BIO *cert; + int verbose = 1; + + if (!PyArg_ParseTuple(args, "s|i:test_decode_certificate", &filename, &verbose)) + return NULL; + + if ((cert=BIO_new(BIO_s_file())) == NULL) { + PyErr_SetString(PySSLErrorObject, "Can't malloc memory to read file"); + goto fail0; + } + + if (BIO_read_filename(cert,filename) <= 0) { + PyErr_SetString(PySSLErrorObject, "Can't open file"); + goto fail0; + } + + x = PEM_read_bio_X509_AUX(cert,NULL, NULL, NULL); + if (x == NULL) { + PyErr_SetString(PySSLErrorObject, "Error decoding PEM-encoded file"); + goto fail0; + } + + retval = _decode_certificate(x, verbose); + + fail0: + + if (cert != NULL) BIO_free(cert); + return retval; +} + + +static PyObject * +PySSL_peercert(PySSLObject *self, PyObject *args) +{ + PyObject *retval = NULL; + int len; + int verification; + PyObject *binary_mode = Py_None; + + if (!PyArg_ParseTuple(args, "|O:peer_certificate", &binary_mode)) + return NULL; + + if (!self->peer_cert) + Py_RETURN_NONE; + + if (PyObject_IsTrue(binary_mode)) { + /* return cert in DER-encoded format */ + + unsigned char *bytes_buf = NULL; + + bytes_buf = NULL; + len = i2d_X509(self->peer_cert, &bytes_buf); + if (len < 0) { + PySSL_SetError(self, len, __FILE__, __LINE__); + return NULL; + } + retval = PyString_FromStringAndSize((const char *) bytes_buf, len); + OPENSSL_free(bytes_buf); + return retval; + + } else { + + verification = SSL_CTX_get_verify_mode(self->ctx); + if ((verification & SSL_VERIFY_PEER) == 0) + return PyDict_New(); + else + return _decode_certificate (self->peer_cert, 0); + } +} + +PyDoc_STRVAR(PySSL_peercert_doc, +"peer_certificate([der=False]) -> certificate\n\ +\n\ +Returns the certificate for the peer. If no certificate was provided,\n\ +returns None. If a certificate was provided, but not validated, returns\n\ +an empty dictionary. Otherwise returns a dict containing information\n\ +about the peer certificate.\n\ +\n\ +If the optional argument is True, returns a DER-encoded copy of the\n\ +peer certificate, or None if no certificate was provided. This will\n\ +return the certificate even if it wasn't validated."); + +static PyObject *PySSL_cipher (PySSLObject *self) { + + PyObject *retval, *v; + SSL_CIPHER *current; + char *cipher_name; + char *cipher_protocol; + + if (self->ssl == NULL) + return Py_None; + current = SSL_get_current_cipher(self->ssl); + if (current == NULL) + return Py_None; + + retval = PyTuple_New(3); + if (retval == NULL) + return NULL; + + cipher_name = (char *) SSL_CIPHER_get_name(current); + if (cipher_name == NULL) { + PyTuple_SET_ITEM(retval, 0, Py_None); + } else { + v = PyString_FromString(cipher_name); + if (v == NULL) + goto fail0; + PyTuple_SET_ITEM(retval, 0, v); + } + cipher_protocol = SSL_CIPHER_get_version(current); + if (cipher_protocol == NULL) { + PyTuple_SET_ITEM(retval, 1, Py_None); + } else { + v = PyString_FromString(cipher_protocol); + if (v == NULL) + goto fail0; + PyTuple_SET_ITEM(retval, 1, v); + } + v = PyInt_FromLong(SSL_CIPHER_get_bits(current, NULL)); + if (v == NULL) + goto fail0; + PyTuple_SET_ITEM(retval, 2, v); + return retval; + + fail0: + Py_DECREF(retval); + return NULL; +} + static void PySSL_dealloc(PySSLObject *self) { if (self->peer_cert) /* Possible not to have one? */ @@ -636,9 +1100,9 @@ check_socket_and_wait_for_timeout(PySocketSockObject *s, int writing) /* s->sock_timeout is in seconds, timeout in ms */ timeout = (int)(s->sock_timeout * 1000 + 0.5); - Py_BEGIN_ALLOW_THREADS + PySSL_BEGIN_ALLOW_THREADS rc = poll(&pollfd, 1, timeout); - Py_END_ALLOW_THREADS + PySSL_END_ALLOW_THREADS goto normal_return; } @@ -657,12 +1121,12 @@ check_socket_and_wait_for_timeout(PySocketSockObject *s, int writing) FD_SET(s->sock_fd, &fds); /* See if the socket is ready */ - Py_BEGIN_ALLOW_THREADS + PySSL_BEGIN_ALLOW_THREADS if (writing) rc = select(s->sock_fd+1, NULL, &fds, NULL, &tv); else rc = select(s->sock_fd+1, &fds, NULL, NULL, &tv); - Py_END_ALLOW_THREADS + PySSL_END_ALLOW_THREADS normal_return: /* Return SOCKET_TIMED_OUT on timeout, SOCKET_OPERATION_OK otherwise @@ -697,10 +1161,10 @@ static PyObject *PySSL_SSLwrite(PySSLObject *self, PyObject *args) } do { err = 0; - Py_BEGIN_ALLOW_THREADS + PySSL_BEGIN_ALLOW_THREADS len = SSL_write(self->ssl, data, count); err = SSL_get_error(self->ssl, len); - Py_END_ALLOW_THREADS + PySSL_END_ALLOW_THREADS if(PyErr_CheckSignals()) { return NULL; } @@ -752,9 +1216,9 @@ static PyObject *PySSL_SSLread(PySSLObject *self, PyObject *args) return NULL; /* first check if there are bytes ready to be read */ - Py_BEGIN_ALLOW_THREADS + PySSL_BEGIN_ALLOW_THREADS count = SSL_pending(self->ssl); - Py_END_ALLOW_THREADS + PySSL_END_ALLOW_THREADS if (!count) { sockstate = check_socket_and_wait_for_timeout(self->Socket, 0); @@ -785,10 +1249,10 @@ static PyObject *PySSL_SSLread(PySSLObject *self, PyObject *args) } do { err = 0; - Py_BEGIN_ALLOW_THREADS + PySSL_BEGIN_ALLOW_THREADS count = SSL_read(self->ssl, PyString_AsString(buf), len); err = SSL_get_error(self->ssl, count); - Py_END_ALLOW_THREADS + PySSL_END_ALLOW_THREADS if(PyErr_CheckSignals()) { Py_DECREF(buf); return NULL; @@ -831,7 +1295,7 @@ PyDoc_STRVAR(PySSL_SSLread_doc, \n\ Read up to len bytes from the SSL socket."); -static PyObject *PySSL_SSLshutdown(PySSLObject *self, PyObject *args) +static PyObject *PySSL_SSLshutdown(PySSLObject *self) { int err; @@ -842,13 +1306,13 @@ static PyObject *PySSL_SSLshutdown(PySSLObject *self, PyObject *args) return NULL; } - Py_BEGIN_ALLOW_THREADS + PySSL_BEGIN_ALLOW_THREADS err = SSL_shutdown(self->ssl); if (err == 0) { /* we need to call it again to finish the shutdown */ err = SSL_shutdown(self->ssl); } - Py_END_ALLOW_THREADS + PySSL_END_ALLOW_THREADS if (err < 0) return PySSL_SetError(self, err, __FILE__, __LINE__); @@ -866,12 +1330,14 @@ the underlying socket object."); static PyMethodDef PySSLMethods[] = { {"write", (PyCFunction)PySSL_SSLwrite, METH_VARARGS, - PySSL_SSLwrite_doc}, + PySSL_SSLwrite_doc}, {"read", (PyCFunction)PySSL_SSLread, METH_VARARGS, - PySSL_SSLread_doc}, + PySSL_SSLread_doc}, {"server", (PyCFunction)PySSL_server, METH_NOARGS}, {"issuer", (PyCFunction)PySSL_issuer, METH_NOARGS}, - {"peer_certificate", (PyCFunction)PySSL_peercert, METH_NOARGS}, + {"peer_certificate", (PyCFunction)PySSL_peercert, METH_VARARGS, + PySSL_peercert_doc}, + {"cipher", (PyCFunction)PySSL_cipher, METH_NOARGS}, {"shutdown", (PyCFunction)PySSL_SSLshutdown, METH_NOARGS, PySSL_SSLshutdown_doc}, {NULL, NULL} @@ -921,7 +1387,7 @@ PyDoc_STRVAR(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."); +bound on the entropy contained in string. See RFC 1750."); static PyObject * PySSL_RAND_status(PyObject *self) @@ -958,9 +1424,9 @@ PySSL_RAND_egd(PyObject *self, PyObject *arg) PyDoc_STRVAR(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 ssl.sslerror if connection to EGD fails or\n\ -if it does provide enough data to seed PRNG."); +Queries the entropy gather daemon (EGD) on the socket named by 'path'.\n\ +Returns number of bytes read. Raises SSLError if connection to EGD\n\ +fails or if it does provide enough data to seed PRNG."); #endif @@ -969,6 +1435,8 @@ if it does provide enough data to seed PRNG."); static PyMethodDef PySSL_methods[] = { {"sslwrap", PySSL_sslwrap, METH_VARARGS, ssl_doc}, + {"_test_decode_cert", PySSL_test_decode_certificate, + METH_VARARGS}, #ifdef HAVE_OPENSSL_RAND {"RAND_add", PySSL_RAND_add, METH_VARARGS, PySSL_RAND_add_doc}, @@ -981,6 +1449,73 @@ static PyMethodDef PySSL_methods[] = { }; +#ifdef WITH_THREAD + +/* an implementation of OpenSSL threading operations in terms + of the Python C thread library */ + +static PyThread_type_lock *_ssl_locks = NULL; + +static unsigned long _ssl_thread_id_function (void) { + return PyThread_get_thread_ident(); +} + +static void _ssl_thread_locking_function (int mode, int n, const char *file, int line) { + /* this function is needed to perform locking on shared data + structures. (Note that OpenSSL uses a number of global data + structures that will be implicitly shared whenever multiple threads + use OpenSSL.) Multi-threaded applications will crash at random if + it is not set. + + locking_function() must be able to handle up to CRYPTO_num_locks() + different mutex locks. It sets the n-th lock if mode & CRYPTO_LOCK, and + releases it otherwise. + + file and line are the file number of the function setting the + lock. They can be useful for debugging. + */ + + if ((_ssl_locks == NULL) || + (n < 0) || (n >= _ssl_locks_count)) + return; + + if (mode & CRYPTO_LOCK) { + PyThread_acquire_lock(_ssl_locks[n], 1); + } else { + PyThread_release_lock(_ssl_locks[n]); + } +} + +static int _setup_ssl_threads(void) { + + int i; + + if (_ssl_locks == NULL) { + _ssl_locks_count = CRYPTO_num_locks(); + _ssl_locks = (PyThread_type_lock *) + malloc(sizeof(PyThread_type_lock) * _ssl_locks_count); + if (_ssl_locks == NULL) + return 0; + memset(_ssl_locks, 0, sizeof(PyThread_type_lock) * _ssl_locks_count); + for (i = 0; i < _ssl_locks_count; i++) { + _ssl_locks[i] = PyThread_allocate_lock(); + if (_ssl_locks[i] == NULL) { + int j; + for (j = 0; j < i; j++) { + PyThread_free_lock(_ssl_locks[j]); + } + free(_ssl_locks); + return 0; + } + } + CRYPTO_set_locking_callback(_ssl_thread_locking_function); + CRYPTO_set_id_callback(_ssl_thread_id_function); + } + return 1; +} + +#endif /* def HAVE_THREAD */ + PyDoc_STRVAR(module_doc, "Implementation module for SSL socket operations. See the socket module\n\ for documentation."); @@ -1003,15 +1538,21 @@ init_ssl(void) /* Init OpenSSL */ SSL_load_error_strings(); +#ifdef WITH_THREAD + /* note that this will start threading if not already started */ + if (!_setup_ssl_threads()) { + return; + } +#endif SSLeay_add_ssl_algorithms(); /* Add symbols to module dict */ - PySSLErrorObject = PyErr_NewException("ssl.sslerror", + PySSLErrorObject = PyErr_NewException("ssl.SSLError", PySocketModule.error, NULL); if (PySSLErrorObject == NULL) return; - if (PyDict_SetItemString(d, "sslerror", PySSLErrorObject) != 0) + if (PyDict_SetItemString(d, "SSLError", PySSLErrorObject) != 0) return; if (PyDict_SetItemString(d, "SSLType", (PyObject *)&PySSL_Type) != 0) |