diff options
author | Jean-Paul Calderone <exarkun@divmod.com> | 2008-12-31 16:15:30 -0500 |
---|---|---|
committer | Jean-Paul Calderone <exarkun@divmod.com> | 2008-12-31 16:15:30 -0500 |
commit | 3329c3045f7e807ac8ea4673b1031851bd7138c8 (patch) | |
tree | f0586e7d63c047fbb8df2492e7db1b69cc5a0b68 | |
parent | a4be22af9b16022cb58c2cdb97c0e06ff40f07d2 (diff) | |
parent | f8c5fabe13692d533b349d501eea7a13efbf81a5 (diff) | |
download | pyopenssl-3329c3045f7e807ac8ea4673b1031851bd7138c8.tar.gz |
update to trunk tip
-rw-r--r-- | ChangeLog | 39 | ||||
-rw-r--r-- | MANIFEST.in | 3 | ||||
-rw-r--r-- | doc/pyOpenSSL.tex | 39 | ||||
-rwxr-xr-x | setup.py | 8 | ||||
-rw-r--r-- | src/crypto/crypto.c | 2 | ||||
-rw-r--r-- | src/crypto/x509.c | 8 | ||||
-rw-r--r-- | src/crypto/x509ext.c | 152 | ||||
-rw-r--r-- | src/crypto/x509name.c | 11 | ||||
-rw-r--r-- | src/crypto/x509req.c | 52 | ||||
-rw-r--r-- | src/rand/rand.c | 6 | ||||
-rw-r--r-- | src/ssl/context.c | 60 | ||||
-rw-r--r-- | src/ssl/ssl.c | 15 | ||||
-rw-r--r-- | test/test_crypto.py | 76 | ||||
-rw-r--r-- | test/test_ssl.py | 189 |
14 files changed, 537 insertions, 123 deletions
@@ -1,3 +1,42 @@ +2008-12-31 Jean-Paul Calderone <exarkun@twistedmatrix.com> + + * src/crypto/x509ext.c, test/test_crypto.py: Add the get_short_name + method to X509Extension based on patch from Alex Stapleton. + +2008-12-31 Jean-Paul Calderone <exarkun@twistedmatrix.com> + + * src/crypto/x509ext.c, test/test_crypto.py: Fix X509Extension so + that it is possible to instantiate extensions which use s2i or r2i + instead of v2i (an extremely obscure extension implementation + detail). + +2008-12-30 Jean-Paul Calderone <exarkun@twistedmatrix.com> + + * MANIFEST.in, src/crypto/crypto.c, src/crypto/x509.c, + src/crypto/x509name.c, src/rand/rand.c, src/ssl/context.c: Changes + which eliminate compiler warnings but should not change any + behavior. + +2008-12-28 Jean-Paul Calderone <exarkun@twistedmatrix.com> + + * test/test_ssl.py, src/ssl/ssl.c: Expose DTLS-related constants, + OP_NO_QUERY_MTU, OP_COOKIE_EXCHANGE, and OP_NO_TICKET. + +2008-12-28 Jean-Paul Calderone <exarkun@twistedmatrix.com> + + * src/ssl/context.c: Add a capath parameter to + Context.load_verify_locations to allow Python code to specify + either or both arguments to the underlying + SSL_CTX_load_verify_locations API. + * src/ssl/context.c: Add Context.set_default_verify_paths, a wrapper + around SSL_CTX_set_default_verify_paths. + +2008-12-28 Jean-Paul Calderone <exarkun@twistedmatrix.com> + + * test/test_crypto.py, src/crypto/x509req.c: Added get_version and + set_version_methods to X509ReqType based on patch from Wouter van + Bommel. Resolves LP#274418. + 2008-09-22 Jean-Paul Calderone <exarkun@twistedmatrix.com> * Release 0.8 diff --git a/MANIFEST.in b/MANIFEST.in index 2506b7b..9f7aa73 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,7 +4,4 @@ include doc/pyOpenSSL.tex doc/Makefile recursive-include doc/tools * recursive-include examples * recursive-include rpm * -global-exclude .cvsignore global-exclude *.pyc - -# @(#) $Id: MANIFEST.in,v 1.11 2003/06/15 09:51:19 martin Exp $ diff --git a/doc/pyOpenSSL.tex b/doc/pyOpenSSL.tex index 4252cac..ebd08ff 100644 --- a/doc/pyOpenSSL.tex +++ b/doc/pyOpenSSL.tex @@ -370,6 +370,9 @@ Return the hash of the certificate subject. \begin{methoddesc}[X509]{digest}{digest_name} Return a digest of the certificate, using the \var{digest_name} method. +\var{digest_name} must be a string describing a digest algorithm supported +by OpenSSL (by EVP_get_digestbyname, specifically). For example, +\constant{"md5"} or \constant{"sha1"}. \end{methoddesc} \begin{methoddesc}[X509]{add_extensions}{extensions} @@ -454,6 +457,15 @@ algorithm identified by the string \var{digest}. Verify a certificate request using the public key \var{pkey}. \end{methoddesc} +\begin{methoddesc}[X509Req]{set_version}{version} +Set the version (RFC 2459, 4.1.2.1) of the certificate request to +\var{version}. +\end{methoddesc} + +\begin{methoddesc}[X509Req]{get_version}{} +Get the version (RFC 2459, 4.1.2.1) of the certificate request. +\end{methoddesc} + \subsubsection{X509Store objects \label{openssl-x509store}} The X509Store object has currently just one method: @@ -522,12 +534,16 @@ None if no CA certificates are present. \subsubsection{X509Extension objects \label{openssl-509ext}} -X509Extension objects currently only have one method: +X509Extension objects have several methods: \begin{methoddesc}[X509Extension]{get_critical}{} Return the critical field of the extension object. \end{methoddesc} +\begin{methoddesc}[X509Extension]{get_short_name}{} +Return the short type name of the extension object. +\end{methoddesc} + \subsubsection{NetscapeSPKI objects \label{openssl-netscape-spki}} NetscapeSPKI objects have the following methods: @@ -545,7 +561,10 @@ Set the public key of the object to \var{key}. \end{methoddesc} \begin{methoddesc}[NetscapeSPKI]{sign}{key, digest_name} -Sign the NetscapeSPKI object using the given \var{key} and \var{digest_name}. +Sign the NetscapeSPKI object using the given \var{key} and +\var{digest_name}. \var{digest_name} must be a string describing a digest +algorithm supported by OpenSSL (by EVP_get_digestbyname, specifically). For +example, \constant{"md5"} or \constant{"sha1"}. \end{methoddesc} \begin{methoddesc}[NetscapeSPKI]{verify}{key} @@ -747,7 +766,7 @@ Retrieve the Context object's verify depth, as set by \end{methoddesc} \begin{methoddesc}[Context]{get_verify_mode}{} -Retrieve the Context object's verify mode, as set by \method{set_verify_mode}. +Retrieve the Context object's verify mode, as set by \method{set_verify}. \end{methoddesc} \begin{methoddesc}[Context]{load_client_ca}{pemfile} @@ -755,9 +774,17 @@ Read a file with PEM-formatted certificates that will be sent to the client when requesting a client certificate. \end{methoddesc} -\begin{methoddesc}[Context]{load_verify_locations}{pemfile} -Specify where CA certificates for verification purposes are located. These are -trusted certificates. Note that the certificates have to be in PEM format. +\begin{methoddesc}[Context]{load_verify_locations}{pemfile, capath} +Specify where CA certificates for verification purposes are located. These +are trusted certificates. Note that the certificates have to be in PEM +format. If capath is passed, it must be a directory prepared using the +\code{c_rehash} tool included with OpenSSL. Either, but not both, of +\var{pemfile} or \var{capath} may be \code{None}. +\end{methoddesc} + +\begin{methoddesc}[Context]{set_default_verify_paths}{} +Specify that the platform provided CA certificates are to be used for +verification purposes. \end{methoddesc} \begin{methoddesc}[Context]{load_tmp_dh}{dhfile} @@ -102,7 +102,13 @@ LibraryDirs = None # Add more platforms here when needed if os.name == 'nt' or sys.platform == 'win32': Libraries = ['eay32', 'Ws2_32'] - ExtraObjects = [r"c:\Python25\libs\ssleay32.a"] + # Try to find it... + for path in ["c:/Python25/libs/", "C:/Python26/libs/", "C:/OpenSSL/lib/MinGW/"]: + if os.path.exists(os.path.join(path, "ssleay32.a")): + ExtraObjects = [os.path.join(path, "ssleay32.a")] + break + else: + raise SystemExit("Cannot find ssleay32.a, aborting") else: Libraries = ['ssl', 'crypto'] ExtraObjects = [] diff --git a/src/crypto/crypto.c b/src/crypto/crypto.c index d239a3b..a2b62c4 100644 --- a/src/crypto/crypto.c +++ b/src/crypto/crypto.c @@ -734,7 +734,7 @@ static int init_openssl_threads(void) { for (i = 0; i < CRYPTO_num_locks(); ++i) { mutex_buf[i] = PyThread_allocate_lock(); } - CRYPTO_set_id_callback(PyThread_get_thread_ident); + CRYPTO_set_id_callback((unsigned long (*)(void))PyThread_get_thread_ident); CRYPTO_set_locking_callback(locking_function); return 1; } diff --git a/src/crypto/x509.c b/src/crypto/x509.c index cc56c2c..90a213b 100644 --- a/src/crypto/x509.c +++ b/src/crypto/x509.c @@ -365,7 +365,7 @@ _set_asn1_time(char *format, ASN1_TIME* timestamp, crypto_X509Obj *self, PyObjec ASN1_GENERALIZEDTIME dummy; dummy.type = V_ASN1_GENERALIZEDTIME; dummy.length = strlen(when); - dummy.data = when; + dummy.data = (unsigned char *)when; if (!ASN1_GENERALIZEDTIME_check(&dummy)) { PyErr_SetString(PyExc_ValueError, "Invalid string"); } else { @@ -440,14 +440,14 @@ _get_asn1_time(char *format, ASN1_TIME* timestamp, crypto_X509Obj *self, PyObjec Py_INCREF(Py_None); return Py_None; } else if (timestamp->type == V_ASN1_GENERALIZEDTIME) { - return PyString_FromString(timestamp->data); + return PyString_FromString((char *)timestamp->data); } else { ASN1_TIME_to_generalizedtime(timestamp, >_timestamp); if (gt_timestamp == NULL) { exception_from_error_queue(); return NULL; } else { - py_timestamp = PyString_FromString(gt_timestamp->data); + py_timestamp = PyString_FromString((char *)gt_timestamp->data); ASN1_GENERALIZEDTIME_free(gt_timestamp); return py_timestamp; } @@ -652,7 +652,7 @@ crypto_X509_digest(crypto_X509Obj *self, PyObject *args) unsigned char fp[EVP_MAX_MD_SIZE]; char *tmp; char *digest_name; - int len,i; + unsigned int len,i; PyObject *ret; const EVP_MD *digest; diff --git a/src/crypto/x509ext.c b/src/crypto/x509ext.c index a898842..3b3c814 100644 --- a/src/crypto/x509ext.c +++ b/src/crypto/x509ext.c @@ -30,6 +30,31 @@ crypto_X509Extension_get_critical(crypto_X509ExtensionObj *self, PyObject *args) return PyInt_FromLong(X509_EXTENSION_get_critical(self->x509_extension)); } +static char crypto_X509Extension_get_short_name_doc[] = "\n\ +Returns the short version of the type name of the X509Extension\n\ +\n\ +Arguments: self - The X509Extension object\n\ + args - The argument tuple, should be empty\n\ +Returns: The short type name.\n\ +"; + +static PyObject * +crypto_X509Extension_get_short_name(crypto_X509ExtensionObj *self, PyObject *args) { + ASN1_OBJECT *obj; + const char *extname; + + if (!PyArg_ParseTuple(args, ":get_short_name")) { + return NULL; + } + + /* Returns an internal pointer to x509_extension, not a copy */ + obj = X509_EXTENSION_get_object(self->x509_extension); + + extname = OBJ_nid2sn(OBJ_obj2nid(obj)); + return PyString_FromString(extname); +} + + /* * ADD_METHOD(name) expands to a correct PyMethodDef declaration * { 'name', (PyCFunction)crypto_X509Extension_name, METH_VARARGS } @@ -40,6 +65,7 @@ crypto_X509Extension_get_critical(crypto_X509ExtensionObj *self, PyObject *args) static PyMethodDef crypto_X509Extension_methods[] = { ADD_METHOD(get_critical), + ADD_METHOD(get_short_name), { NULL, NULL } }; #undef ADD_METHOD @@ -55,109 +81,63 @@ static PyMethodDef crypto_X509Extension_methods[] = crypto_X509ExtensionObj * crypto_X509Extension_New(char *type_name, int critical, char *value) { + X509V3_CTX ctx; crypto_X509ExtensionObj *self; - int ext_len, ext_nid; - unsigned char *ext_der; - X509V3_EXT_METHOD *ext_method = NULL; - ASN1_OCTET_STRING *ext_oct; - STACK_OF(CONF_VALUE) *nval; - void * ext_struct; - X509_EXTENSION *extension = NULL; + char* value_with_critical = NULL; - self = PyObject_New(crypto_X509ExtensionObj, &crypto_X509Extension_Type); + /* We have no configuration database - but perhaps we should. Anyhow, the + * context is necessary for any extension which uses the r2i conversion + * method. That is, X509V3_EXT_nconf may segfault if passed a NULL ctx. */ + X509V3_set_ctx_nodb(&ctx); - if (self == NULL) - return NULL; + self = PyObject_New(crypto_X509ExtensionObj, &crypto_X509Extension_Type); - /* Try to get a NID for the name */ - if ((ext_nid = OBJ_sn2nid(type_name)) == NID_undef) - { - PyErr_SetString(PyExc_ValueError, "Unknown extension name"); - return NULL; + if (self == NULL) { + goto error; } - /* Lookup the extension method structure */ - if (!(ext_method = X509V3_EXT_get_nid(ext_nid))) - { - PyErr_SetString(PyExc_ValueError, "Unknown extension"); - return NULL; + self->dealloc = 0; + + /* There are other OpenSSL APIs which would let us pass in critical + * separately, but they're harder to use, and since value is already a pile + * of crappy junk smuggling a ton of utterly important structured data, + * what's the point of trying to avoid nasty stuff with strings? (However, + * X509V3_EXT_i2d in particular seems like it would be a better API to + * invoke. I do not know where to get the ext_struc it desires for its + * last parameter, though.) */ + value_with_critical = malloc(strlen("critical,") + strlen(value) + 1); + if (!value_with_critical) { + goto critical_malloc_error; } - /* Look if it has a function to convert value to an - * internal structure. - */ - if (!ext_method->v2i) - { - PyErr_SetString(PyExc_ValueError, "Can't initialize exception"); - return NULL; + if (critical) { + strcpy(value_with_critical, "critical,"); + strcpy(value_with_critical + strlen("critical,"), value); + } else { + strcpy(value_with_critical, value); } - /* Parse the value */ - nval = X509V3_parse_list(value); - if (!nval) - { - PyErr_SetString(PyExc_ValueError, "Invalid extension string"); - return NULL; - } + self->x509_extension = X509V3_EXT_nconf( + NULL, &ctx, type_name, value_with_critical); - /* And use it to get the internal structure */ - if(!(ext_struct = ext_method->v2i(ext_method, NULL, nval))) { - exception_from_error_queue(); - return NULL; - } + free(value_with_critical); - /* Deallocate the configuration value stack */ - sk_CONF_VALUE_pop_free(nval, X509V3_conf_free); - - /* Find out how much memory we need */ - - - /* Convert internal representation to DER */ - /* and Allocate */ - if (ext_method->it) { - ext_der = NULL; - ext_len = ASN1_item_i2d(ext_struct, &ext_der, ASN1_ITEM_ptr(ext_method->it)); - if (ext_len < 0) { - PyErr_SetString(PyExc_MemoryError, "Could not allocate memory"); - return NULL; - } - } else { - unsigned char *p; - ext_len = ext_method->i2d(ext_struct, NULL); - if(!(ext_der = malloc(ext_len))) { - PyErr_SetString(PyExc_MemoryError, "Could not allocate memory"); - return NULL; - } - p = ext_der; - ext_method->i2d(ext_struct, &p); + if (!self->x509_extension) { + goto nconf_error; } - /* And create the ASN1_OCTET_STRING */ - if(!(ext_oct = M_ASN1_OCTET_STRING_new())) { - exception_from_error_queue(); - return NULL; - } - - ext_oct->data = ext_der; - ext_oct->length = ext_len; + self->dealloc = 1; + return self; - /* Now that we got all ingredients, make the extension */ - extension = X509_EXTENSION_create_by_NID(NULL, ext_nid, critical, ext_oct); - if (extension == NULL) - { - exception_from_error_queue(); - M_ASN1_OCTET_STRING_free(ext_oct); - ext_method->ext_free(ext_struct); - return NULL; - } - - M_ASN1_OCTET_STRING_free(ext_oct); - /* ext_method->ext_free(ext_struct); */ + nconf_error: + exception_from_error_queue(); - self->x509_extension = extension; - self->dealloc = 1; + critical_malloc_error: + PyObject_Free(self); + + error: + return NULL; - return self; } /* diff --git a/src/crypto/x509name.c b/src/crypto/x509name.c index d304591..8871ab1 100644 --- a/src/crypto/x509name.c +++ b/src/crypto/x509name.c @@ -13,7 +13,7 @@ #define crypto_MODULE #include "crypto.h" -static PyMethodDef crypto_X509Name_methods[]; +static PyMethodDef crypto_X509Name_methods[4]; /* * Constructor for X509Name, never called by Python code directly @@ -102,8 +102,9 @@ set_name_by_nid(X509_NAME *name, int nid, char *utf8string) } /* Add the new entry */ - if (!X509_NAME_add_entry_by_NID(name, nid, MBSTRING_UTF8, utf8string, - -1, -1, 0)) + if (!X509_NAME_add_entry_by_NID(name, nid, MBSTRING_UTF8, + (unsigned char *)utf8string, + -1, -1, 0)) { exception_from_error_queue(); return -1; @@ -193,7 +194,7 @@ crypto_X509Name_compare(crypto_X509NameObj *n, crypto_X509NameObj *m) return -1; } else if (result > 0) { return 1; - } else if (result == 0) { + } else { return 0; } } @@ -318,7 +319,7 @@ crypto_X509Name_get_components(crypto_X509NameObj *self, PyObject *args) tuple = PyTuple_New(2); PyTuple_SetItem(tuple, 0, PyString_FromString(OBJ_nid2sn(nid))); - PyTuple_SetItem(tuple, 1, PyString_FromStringAndSize(str, l)); + PyTuple_SetItem(tuple, 1, PyString_FromStringAndSize((char *)str, l)); PyList_SetItem(list, i, tuple); } diff --git a/src/crypto/x509req.c b/src/crypto/x509req.c index f367360..bb6385f 100644 --- a/src/crypto/x509req.c +++ b/src/crypto/x509req.c @@ -239,6 +239,56 @@ crypto_X509Req_add_extensions(crypto_X509ReqObj *self, PyObject *args) return Py_None; } +static char crypto_X509Req_set_version_doc[] = "\n\ +Set the version subfield (RFC 2459, section 4.1.2.1) of the certificate\n\ +request.\n\ +\n\ +Arguments: self - X509Req object\n\ + args - The Python argument tuple, should be:\n\ + version - The version number\n\ +Returns: None\n\ +"; + +static PyObject * +crypto_X509Req_set_version(crypto_X509ReqObj *self, PyObject *args) +{ + long version; + + if (!PyArg_ParseTuple(args, "l:set_version", &version)) { + return NULL; + } + + if (!X509_REQ_set_version(self->x509_req, version)) { + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +static char crypto_X509Req_get_version_doc[] = "\n\ +Get the version subfield (RFC 2459, section 4.1.2.1) of the certificate\n\ +request.\n\ +\n\ +Arguments: self - X509Req object\n\ + args - The Python argument tuple, should be empty.\n\ +Returns: an integer giving the value of the version subfield\n\ +"; + +static PyObject * +crypto_X509Req_get_version(crypto_X509ReqObj *self, PyObject *args) +{ + long version; + + if (!PyArg_ParseTuple(args, ":get_version")) { + return NULL; + } + + version = X509_REQ_get_version(self->x509_req); + + return PyLong_FromLong(version); +} + /* * ADD_METHOD(name) expands to a correct PyMethodDef declaration * { 'name', (PyCFunction)crypto_X509Req_name, METH_VARARGS } @@ -254,6 +304,8 @@ static PyMethodDef crypto_X509Req_methods[] = ADD_METHOD(sign), ADD_METHOD(verify), ADD_METHOD(add_extensions), + ADD_METHOD(set_version), + ADD_METHOD(get_version), { NULL, NULL } }; #undef ADD_METHOD diff --git a/src/rand/rand.c b/src/rand/rand.c index 9802a92..ff89ebb 100644 --- a/src/rand/rand.c +++ b/src/rand/rand.c @@ -15,8 +15,10 @@ * WIN32 or WINDOWS needs to be defined, otherwise we get a * warning. */ -#ifdef MS_WINDOWS -#define WIN32 +#ifdef MS_WINDOWS +# ifndef WIN32 +# define WIN32 +# endif #endif #include <openssl/rand.h> diff --git a/src/ssl/context.c b/src/ssl/context.c index ed0eabe..1fecc9b 100644 --- a/src/ssl/context.c +++ b/src/ssl/context.c @@ -11,6 +11,12 @@ */ #include <Python.h> +#if PY_VERSION_HEX >= 0x02050000 +# define PYARG_PARSETUPLE_FORMAT const char +#else +# define PYARG_PARSETUPLE_FORMAT char +#endif + #ifndef MS_WINDOWS # include <sys/socket.h> # include <netinet/in.h> @@ -156,7 +162,7 @@ global_verify_callback(int ok, X509_STORE_CTX *x509_ctx) SSL *ssl; ssl_ConnectionObj *conn; crypto_X509Obj *cert; - int errnum, errdepth, c_ret, use_thread_state; + int errnum, errdepth, c_ret; // Get Connection object to check thread state ssl = (SSL *)X509_STORE_CTX_get_app_data(x509_ctx); @@ -197,7 +203,7 @@ global_verify_callback(int ok, X509_STORE_CTX *x509_ctx) * Returns: None */ static void -global_info_callback(SSL *ssl, int where, int _ret) +global_info_callback(const SSL *ssl, int where, int _ret) { ssl_ConnectionObj *conn = (ssl_ConnectionObj *)SSL_get_app_data(ssl); PyObject *argv, *ret; @@ -238,18 +244,20 @@ chain\n\ \n\ Arguments: self - The Context object\n\ args - The Python argument tuple, should be:\n\ - cafile - Which file we can find the certificates\n\ + cafile - In which file we can find the certificates\n\ + capath - In which directory we can find the certificates\r\ Returns: None\n\ "; static PyObject * -ssl_Context_load_verify_locations(ssl_ContextObj *self, PyObject *args) -{ - char *cafile; +ssl_Context_load_verify_locations(ssl_ContextObj *self, PyObject *args) { + char *cafile = NULL; + char *capath = NULL; - if (!PyArg_ParseTuple(args, "s:load_verify_locations", &cafile)) + if (!PyArg_ParseTuple(args, "z|z:load_verify_locations", &cafile, &capath)) { return NULL; + } - if (!SSL_CTX_load_verify_locations(self->ctx, cafile, NULL)) + if (!SSL_CTX_load_verify_locations(self->ctx, cafile, capath)) { exception_from_error_queue(); return NULL; @@ -261,6 +269,33 @@ ssl_Context_load_verify_locations(ssl_ContextObj *self, PyObject *args) } } +static char ssl_Context_set_default_verify_paths_doc[] = "\n\ +Use the platform-specific CA certificate locations\n\ +\n\ +Arguments: self - The Context object\n\ + args - None\n\ +\n\ +Returns: None\n\ +"; +static PyObject * +ssl_Context_set_default_verify_paths(ssl_ContextObj *self, PyObject *args) { + if (!PyArg_ParseTuple(args, ":set_default_verify_paths")) { + return NULL; + } + + /* + * XXX Error handling for SSL_CTX_set_default_verify_paths is untested. + * -exarkun + */ + if (!SSL_CTX_set_default_verify_paths(self->ctx)) { + exception_from_error_queue(); + return NULL; + } + Py_INCREF(Py_None); + return Py_None; +}; + + static char ssl_Context_set_passwd_cb_doc[] = "\n\ Set the passphrase callback\n\ \n\ @@ -314,7 +349,7 @@ parse_certificate_argument(const char* format1, const char* format2, PyObject* a if (!crypto_X509_type) { - if (!PyArg_ParseTuple(args, format1, &cert)) + if (!PyArg_ParseTuple(args, (PYARG_PARSETUPLE_FORMAT *)format1, &cert)) return NULL; if (strcmp(cert->ob_type->tp_name, "X509") != 0 || @@ -327,7 +362,7 @@ parse_certificate_argument(const char* format1, const char* format2, PyObject* a crypto_X509_type = cert->ob_type; } else - if (!PyArg_ParseTuple(args, format2, crypto_X509_type, + if (!PyArg_ParseTuple(args, (PYARG_PARSETUPLE_FORMAT *)format2, crypto_X509_type, &cert)) return NULL; return cert; @@ -611,8 +646,8 @@ Returns: None\n\ static PyObject * ssl_Context_set_session_id(ssl_ContextObj *self, PyObject *args) { - char *buf; - int len; + unsigned char *buf; + unsigned int len; if (!PyArg_ParseTuple(args, "s#:set_session_id", &buf, &len)) return NULL; @@ -952,6 +987,7 @@ ssl_Context_set_options(ssl_ContextObj *self, PyObject *args) static PyMethodDef ssl_Context_methods[] = { ADD_METHOD(load_verify_locations), ADD_METHOD(set_passwd_cb), + ADD_METHOD(set_default_verify_paths), ADD_METHOD(use_certificate_chain_file), ADD_METHOD(use_certificate_file), ADD_METHOD(use_certificate), diff --git a/src/ssl/ssl.c b/src/ssl/ssl.c index 1f8cbcc..f1c51aa 100644 --- a/src/ssl/ssl.c +++ b/src/ssl/ssl.c @@ -193,7 +193,20 @@ do { \ PyModule_AddIntConstant(module, "OP_NETSCAPE_CA_DN_BUG", SSL_OP_NETSCAPE_CA_DN_BUG); PyModule_AddIntConstant(module, "OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG", SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG); - /* For SSL_set_shutdown */ + /* DTLS related options. The first two of these were introduced in + * 2005, the third in 2007. To accomodate systems which are still using + * older versions, make them optional. */ +#ifdef SSL_OP_NO_QUERY_MTU + PyModule_AddIntConstant(module, "OP_NO_QUERY_MTU", SSL_OP_NO_QUERY_MTU); +#endif +#ifdef SSL_OP_COOKIE_EXCHANGE + PyModule_AddIntConstant(module, "OP_COOKIE_EXCHANGE", SSL_OP_COOKIE_EXCHANGE); +#endif +#ifdef SSL_OP_NO_TICKET + PyModule_AddIntConstant(module, "OP_NO_TICKET", SSL_OP_NO_TICKET); +#endif + + /* For SSL_set_shutdown */ PyModule_AddIntConstant(module, "SENT_SHUTDOWN", SSL_SENT_SHUTDOWN); PyModule_AddIntConstant(module, "RECEIVED_SHUTDOWN", SSL_RECEIVED_SHUTDOWN); diff --git a/test/test_crypto.py b/test/test_crypto.py index 31ceb2d..902bde1 100644 --- a/test/test_crypto.py +++ b/test/test_crypto.py @@ -9,6 +9,7 @@ from unittest import TestCase from OpenSSL.crypto import TYPE_RSA, TYPE_DSA, Error, PKey, PKeyType from OpenSSL.crypto import X509, X509Type, X509Name, X509NameType from OpenSSL.crypto import X509Req, X509ReqType +from OpenSSL.crypto import X509Extension, X509ExtensionType from OpenSSL.crypto import FILETYPE_PEM, load_certificate, load_privatekey from OpenSSL.crypto import dump_privatekey @@ -83,6 +84,67 @@ class _Python23TestCaseHelper: +class X509ExtTests(TestCase, _Python23TestCaseHelper): + def test_construction(self): + """ + L{X509Extension} accepts an extension type name, a critical flag, + and an extension value and returns an L{X509ExtensionType} instance. + """ + basic = X509Extension('basicConstraints', True, 'CA:true') + self.assertTrue( + isinstance(basic, X509ExtensionType), + "%r is of type %r, should be %r" % ( + basic, type(basic), X509ExtensionType)) + + comment = X509Extension('nsComment', False, 'pyOpenSSL unit test') + self.assertTrue( + isinstance(comment, X509ExtensionType), + "%r is of type %r, should be %r" % ( + comment, type(comment), X509ExtensionType)) + + + def test_invalid_extension(self): + """ + L{X509Extension} raises something if it is passed a bad extension + name or value. + """ + self.assertRaises( + Error, X509Extension, 'thisIsMadeUp', False, 'hi') + self.assertRaises( + Error, X509Extension, 'basicConstraints', False, 'blah blah') + + # Exercise a weird one (an extension which uses the r2i method). This + # exercises the codepath that requires a non-NULL ctx to be passed to + # X509V3_EXT_nconf. It can't work now because we provide no + # configuration database. It might be made to work in the future. + self.assertRaises( + Error, X509Extension, 'proxyCertInfo', True, + 'language:id-ppl-anyLanguage,pathlen:1,policy:text:AB') + + + def test_get_critical(self): + """ + L{X509ExtensionType.get_critical} returns the value of the + extension's critical flag. + """ + ext = X509Extension('basicConstraints', True, 'CA:true') + self.assertTrue(ext.get_critical()) + ext = X509Extension('basicConstraints', False, 'CA:true') + self.assertFalse(ext.get_critical()) + + + def test_get_short_name(self): + """ + L{X509ExtensionType.get_short_name} returns a string giving the short + type name of the extension. + """ + ext = X509Extension('basicConstraints', True, 'CA:true') + self.assertEqual(ext.get_short_name(), 'basicConstraints') + ext = X509Extension('nsComment', True, 'foo bar') + self.assertEqual(ext.get_short_name(), 'nsComment') + + + class PKeyTests(TestCase, _Python23TestCaseHelper): """ Unit tests for L{OpenSSL.crypto.PKey}. @@ -419,6 +481,20 @@ class X509ReqTests(TestCase, _PKeyInteractionTestsMixin, _Python23TestCaseHelper "%r is of type %r, should be %r" % (request, type(request), X509ReqType)) + def test_version(self): + """ + L{X509ReqType.set_version} sets the X.509 version of the certificate + request. L{X509ReqType.get_version} returns the X.509 version of + the certificate request. The initial value of the version is 0. + """ + request = X509Req() + self.assertEqual(request.get_version(), 0) + request.set_version(1) + self.assertEqual(request.get_version(), 1) + request.set_version(3) + self.assertEqual(request.get_version(), 3) + + def test_get_subject(self): """ L{X509ReqType.get_subject} returns an L{X509Name} for the subject of diff --git a/test/test_ssl.py b/test/test_ssl.py index cd07cd5..7fbb359 100644 --- a/test/test_ssl.py +++ b/test/test_ssl.py @@ -7,12 +7,26 @@ Unit tests for L{OpenSSL.SSL}. from unittest import TestCase from tempfile import mktemp from socket import socket +from os import makedirs, symlink +from os.path import join from OpenSSL.crypto import TYPE_RSA, FILETYPE_PEM, PKey, dump_privatekey, load_certificate, load_privatekey -from OpenSSL.SSL import WantReadError, Context, Connection +from OpenSSL.SSL import WantReadError, Context, Connection, Error from OpenSSL.SSL import SSLv2_METHOD, SSLv3_METHOD, SSLv23_METHOD, TLSv1_METHOD - +from OpenSSL.SSL import VERIFY_PEER from OpenSSL.test.test_crypto import _Python23TestCaseHelper, cleartextCertificatePEM, cleartextPrivateKeyPEM +try: + from OpenSSL.SSL import OP_NO_QUERY_MTU +except ImportError: + OP_NO_QUERY_MTU = None +try: + from OpenSSL.SSL import OP_COOKIE_EXCHANGE +except ImportError: + OP_COOKIE_EXCHANGE = None +try: + from OpenSSL.SSL import OP_NO_TICKET +except ImportError: + OP_NO_TICKET = None class ContextTests(TestCase, _Python23TestCaseHelper): @@ -115,3 +129,174 @@ class ContextTests(TestCase, _Python23TestCaseHelper): # Kind of lame. Just make sure it got called somehow. self.assertTrue(called) + + + def _load_verify_locations_test(self, *args): + port = socket() + port.bind(('', 0)) + port.listen(1) + + client = socket() + client.setblocking(False) + client.connect_ex(port.getsockname()) + + clientContext = Context(TLSv1_METHOD) + clientContext.load_verify_locations(*args) + # Require that the server certificate verify properly or the + # connection will fail. + clientContext.set_verify( + VERIFY_PEER, + lambda conn, cert, errno, depth, preverify_ok: preverify_ok) + + clientSSL = Connection(clientContext, client) + clientSSL.set_connect_state() + + server, _ = port.accept() + server.setblocking(False) + + serverContext = Context(TLSv1_METHOD) + serverContext.use_certificate( + load_certificate(FILETYPE_PEM, cleartextCertificatePEM)) + serverContext.use_privatekey( + load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM)) + + serverSSL = Connection(serverContext, server) + serverSSL.set_accept_state() + + for i in range(3): + for ssl in clientSSL, serverSSL: + try: + # Without load_verify_locations above, the handshake + # will fail: + # Error: [('SSL routines', 'SSL3_GET_SERVER_CERTIFICATE', + # 'certificate verify failed')] + ssl.do_handshake() + except WantReadError: + pass + + cert = clientSSL.get_peer_certificate() + self.assertEqual(cert.get_subject().CN, 'pyopenssl.sf.net') + + def test_load_verify_file(self): + """ + L{Context.load_verify_locations} accepts a file name and uses the + certificates within for verification purposes. + """ + cafile = self.mktemp() + fObj = file(cafile, 'w') + fObj.write(cleartextCertificatePEM) + fObj.close() + + self._load_verify_locations_test(cafile) + + + def test_load_verify_invalid_file(self): + """ + L{Context.load_verify_locations} raises L{Error} when passed a + non-existent cafile. + """ + clientContext = Context(TLSv1_METHOD) + self.assertRaises( + Error, clientContext.load_verify_locations, self.mktemp()) + + + def test_load_verify_directory(self): + """ + L{Context.load_verify_locations} accepts a directory name and uses + the certificates within for verification purposes. + """ + capath = self.mktemp() + makedirs(capath) + cafile = join(capath, 'cert.pem') + fObj = file(cafile, 'w') + fObj.write(cleartextCertificatePEM) + fObj.close() + + # Hash value computed manually with c_rehash to avoid depending on + # c_rehash in the test suite. + symlink('cert.pem', join(capath, '07497d9e.0')) + + self._load_verify_locations_test(None, capath) + + + def test_set_default_verify_paths(self): + """ + L{Context.set_default_verify_paths} causes the platform-specific CA + certificate locations to be used for verification purposes. + """ + # Testing this requires a server with a certificate signed by one of + # the CAs in the platform CA location. Getting one of those costs + # money. Fortunately (or unfortunately, depending on your + # perspective), it's easy to think of a public server on the + # internet which has such a certificate. Connecting to the network + # in a unit test is bad, but it's the only way I can think of to + # really test this. -exarkun + + # Arg, verisign.com doesn't speak TLSv1 + context = Context(SSLv3_METHOD) + context.set_default_verify_paths() + context.set_verify( + VERIFY_PEER, + lambda conn, cert, errno, depth, preverify_ok: preverify_ok) + + client = socket() + client.connect(('verisign.com', 443)) + clientSSL = Connection(context, client) + clientSSL.set_connect_state() + clientSSL.do_handshake() + clientSSL.send('GET / HTTP/1.0\r\n\r\n') + self.assertTrue(clientSSL.recv(1024)) + + + def test_set_default_verify_paths_signature(self): + """ + L{Context.set_default_verify_paths} takes no arguments and raises + L{TypeError} if given any. + """ + context = Context(TLSv1_METHOD) + self.assertRaises(TypeError, context.set_default_verify_paths, None) + self.assertRaises(TypeError, context.set_default_verify_paths, 1) + self.assertRaises(TypeError, context.set_default_verify_paths, "") + + + +class ConstantsTests(TestCase): + """ + Tests for the values of constants exposed in L{OpenSSL.SSL}. + + These are values defined by OpenSSL intended only to be used as flags to + OpenSSL APIs. The only assertions it seems can be made about them is + their values. + """ + # unittest.TestCase has no skip mechanism + if OP_NO_QUERY_MTU is not None: + def test_op_no_query_mtu(self): + """ + The value of L{OpenSSL.SSL.OP_NO_QUERY_MTU} is 0x1000, the value of + I{SSL_OP_NO_QUERY_MTU} defined by I{openssl/ssl.h}. + """ + self.assertEqual(OP_NO_QUERY_MTU, 0x1000) + else: + "OP_NO_QUERY_MTU unavailable - OpenSSL version may be too old" + + + if OP_COOKIE_EXCHANGE is not None: + def test_op_cookie_exchange(self): + """ + The value of L{OpenSSL.SSL.OP_COOKIE_EXCHANGE} is 0x2000, the value + of I{SSL_OP_COOKIE_EXCHANGE} defined by I{openssl/ssl.h}. + """ + self.assertEqual(OP_COOKIE_EXCHANGE, 0x2000) + else: + "OP_COOKIE_EXCHANGE unavailable - OpenSSL version may be too old" + + + if OP_NO_TICKET is not None: + def test_op_no_ticket(self): + """ + The value of L{OpenSSL.SSL.OP_NO_TICKET} is 0x4000, the value of + I{SSL_OP_NO_TICKET} defined by I{openssl/ssl.h}. + """ + self.assertEqual(OP_NO_TICKET, 0x4000) + else: + "OP_NO_TICKET unavailable - OpenSSL version may be too old" |