Add support for CRL and Revoked objects.
diff --git a/doc/pyOpenSSL.tex b/doc/pyOpenSSL.tex
index bd83d6d..d43ad96 100644
--- a/doc/pyOpenSSL.tex
+++ b/doc/pyOpenSSL.tex
@@ -207,6 +207,14 @@
method.
\end{classdesc}
+\begin{classdesc}{CRL}{}
+A class representing Certifcate Revocation List objects.
+\end{classdesc}
+
+\begin{classdesc}{Revoked}{}
+A class representing Revocation objects of CRL.
+\end{classdesc}
+
\begin{datadesc}{FILETYPE_PEM}
\dataline{FILETYPE_ASN1}
File type constants.
@@ -259,6 +267,12 @@
pass phrase.
\end{funcdesc}
+\begin{funcdesc}{load_crl}{type, buffer}
+Load Certificate Revocation List (CRL) data from a string \var{buffer}.
+\var{buffer} encoded with the type \var{type}. The type \var{type}
+must either \constant{FILETYPE_PEM} or \constant{FILETYPE_ASN1}).
+\end{funcdesc}
+
\begin{funcdesc}{load_pkcs7_data}{type, buffer}
Load pkcs7 data from the string \var{buffer} encoded with the type \var{type}.
\end{funcdesc}
@@ -579,6 +593,45 @@
Verify the NetscapeSPKI object using the given \var{key}.
\end{methoddesc}
+\subsubsection{CRL objects \label{crl}}
+
+CRL objects have the following methods:
+
+\begin{methoddesc}[CRL]{add_revoked}{revoked}
+Add a Revoked object to the CRL, by value not reference.
+\end{methoddesc}
+
+\begin{methoddesc}[CRL]{export}{cert, key\optional{, type=FILETYPE_PEM}\optional{, days=100}}
+Use \var{cert} and \var{key} to sign the CRL and return the CRL as a string.
+\var{days} is the number of days before the next CRL is due.
+\end{methoddesc}
+
+\begin{methoddesc}[CRL]{get_revoked}{}
+Return a tuple of Revoked objects, by value not reference.
+\end{methoddesc}
+
+\subsubsection{Revoked objects \label{revoked}}
+
+Revoked objects have the following methods:
+
+\begin{methoddesc}[Revoked]{get_rev_date}{}
+Return the revocation date as a str.
+The string is formatted as an ASN1 GENERALIZEDTIME.
+\end{methoddesc}
+
+\begin{methoddesc}[Revoked]{get_serial}{}
+Return a str containing a hex number of the serial of the revoked certificate.
+\end{methoddesc}
+
+\begin{methoddesc}[Revoked]{set_rev_date}{date}
+Set the revocation date.
+The string is formatted as an ASN1 GENERALIZEDTIME.
+\end{methoddesc}
+
+\begin{methoddesc}[Revoked]{set_serial}{serial}
+\var{serial} is a string containing a hex number of the serial of the revoked certificate.
+\end{methoddesc}
+
% % % rand module
diff --git a/setup.py b/setup.py
index a6a446c..7011f9d 100755
--- a/setup.py
+++ b/setup.py
@@ -23,12 +23,14 @@
'src/crypto/x509store.c', 'src/crypto/x509req.c',
'src/crypto/x509ext.c', 'src/crypto/pkcs7.c',
'src/crypto/pkcs12.c', 'src/crypto/netscape_spki.c',
+ 'src/crypto/revoked.c', 'src/crypto/crl.c',
'src/util.c']
crypto_dep = ['src/crypto/crypto.h', 'src/crypto/x509.h',
'src/crypto/x509name.h', 'src/crypto/pkey.h',
'src/crypto/x509store.h', 'src/crypto/x509req.h',
'src/crypto/x509ext.h', 'src/crypto/pkcs7.h',
'src/crypto/pkcs12.h', 'src/crypto/netscape_spki.h',
+ 'src/crypto/revoked.h', 'src/crypto/crl.h',
'src/util.h']
rand_src = ['src/rand/rand.c', 'src/util.c']
rand_dep = ['src/util.h']
diff --git a/src/crypto/crl.c b/src/crypto/crl.c
new file mode 100644
index 0000000..c90cbe8
--- /dev/null
+++ b/src/crypto/crl.c
@@ -0,0 +1,294 @@
+#include <Python.h>
+#define crypto_MODULE
+#include "crypto.h"
+
+
+static X509_REVOKED * X509_REVOKED_dup(X509_REVOKED *orig)
+{
+ X509_REVOKED *dupe = NULL;
+
+ dupe = X509_REVOKED_new();
+ if(dupe == NULL)
+ return NULL;
+ if(orig->serialNumber)
+ {
+ dupe->serialNumber = M_ASN1_INTEGER_dup(orig->serialNumber);
+ }
+ if(orig->revocationDate)
+ {
+ dupe->revocationDate = M_ASN1_INTEGER_dup(orig->revocationDate);
+ }
+ if(orig->extensions)
+ {
+ STACK_OF(X509_EXTENSION) *sk = NULL;
+ X509_EXTENSION * ext;
+ int j;
+
+ sk = sk_X509_EXTENSION_new_null();
+ for(j = 0; j < sk_X509_EXTENSION_num(orig->extensions); j++) {
+ ext = sk_X509_EXTENSION_value(orig->extensions, j);
+ ext = X509_EXTENSION_dup(ext);
+ sk_X509_EXTENSION_push(sk, ext);
+ }
+ dupe->extensions = sk;
+ }
+ dupe->sequence = orig->sequence;
+ return dupe;
+}
+
+static char crypto_CRL_get_revoked_doc[] = "\n\
+Return revoked portion of the CRL structure (by value\n\
+not reference).\n\
+\n\
+@return: A tuple of Revoked objects.\n\
+";
+static PyObject *
+crypto_CRL_get_revoked(crypto_CRLObj *self, PyObject *args)
+{
+ int j, num_rev;
+ X509_REVOKED *r = NULL;
+ PyObject *obj = NULL, *rev_obj;
+
+ if (!PyArg_ParseTuple(args, ":get_revoked"))
+ return NULL;
+
+ num_rev = sk_X509_REVOKED_num(self->crl->crl->revoked);
+ if(num_rev < 0)
+ {
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+ if ((obj = PyTuple_New(num_rev)) == NULL)
+ return NULL;
+
+ for(j = 0; j < num_rev; j++)
+ {
+ r = sk_X509_REVOKED_value(self->crl->crl->revoked, j);
+ r = X509_REVOKED_dup(r);
+ if( r == NULL )
+ goto error;
+ rev_obj = (PyObject *) crypto_Revoked_New(r);
+ if( rev_obj == NULL )
+ goto error;
+ r = NULL; /* it's now owned by rev_obj */
+ PyTuple_SET_ITEM(obj, j, rev_obj);
+ }
+ return obj;
+
+ error:
+ if(r)
+ X509_REVOKED_free(r);
+ Py_XDECREF(obj);
+ return NULL;
+}
+
+static char crypto_CRL_add_revoked_doc[] = "\n\
+Add a revoked (by value not reference) to the CRL structure\n\
+\n\
+@param cert: The new revoked.\n\
+@type cert: L{X509}\n\
+@return: None\n\
+";
+static PyObject *
+crypto_CRL_add_revoked(crypto_CRLObj *self, PyObject *args, PyObject *keywds)
+{
+ crypto_RevokedObj * rev_obj = NULL;
+ static char *kwlist[] = {"revoked", NULL};
+ X509_REVOKED * dup;
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywds, "O!:add_revoked",
+ kwlist, &crypto_Revoked_Type, &rev_obj))
+ return NULL;
+
+ dup = X509_REVOKED_dup( rev_obj->revoked );
+ if(dup == NULL)
+ return NULL;
+ X509_CRL_add0_revoked(self->crl, dup);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static char crypto_CRL_export_doc[] = "\n\
+Export a CRL as a string\n\
+\n\
+@param cert: Used to sign CRL.\n\
+@type cert: L{X509}\n\
+@param key: Used to sign CRL.\n\
+@type key: L{PKey}\n\
+@return: None\n\
+";
+static PyObject *
+crypto_CRL_export(crypto_CRLObj *self, PyObject *args, PyObject *keywds)
+{
+ int ret, buf_len, type = X509_FILETYPE_PEM, days = 100;
+ char *temp;
+ BIO *bio;
+ PyObject *buffer;
+ crypto_PKeyObj *key;
+ ASN1_TIME *tmptm;
+ crypto_X509Obj *x509;
+ static char *kwlist[] = {"cert", "key", "type", "days", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywds, "O!O!|ii:dump_crl", kwlist,
+ &crypto_X509_Type, &x509,
+ &crypto_PKey_Type, &key, &type, &days))
+ return NULL;
+
+ bio=BIO_new(BIO_s_mem());
+ tmptm = ASN1_TIME_new();
+ if (!tmptm)
+ return 0;
+ X509_gmtime_adj(tmptm,0);
+ X509_CRL_set_lastUpdate(self->crl, tmptm);
+ X509_gmtime_adj(tmptm,days*24*60*60);
+ X509_CRL_set_nextUpdate(self->crl, tmptm);
+ ASN1_TIME_free(tmptm);
+ X509_CRL_set_issuer_name(self->crl, X509_get_subject_name(x509->x509));
+ X509_CRL_sign(self->crl, key->pkey, EVP_md5());
+ switch (type)
+ {
+ case X509_FILETYPE_PEM:
+ ret = PEM_write_bio_X509_CRL(bio, self->crl);
+ break;
+
+ case X509_FILETYPE_ASN1:
+ ret = (int) i2d_X509_CRL_bio(bio, self->crl);
+ break;
+
+ case X509_FILETYPE_TEXT:
+ ret = X509_CRL_print(bio, self->crl);
+ break;
+
+ default:
+ PyErr_SetString(PyExc_ValueError,
+ "type argument must be FILETYPE_PEM or FILETYPE_ASN1");
+ return NULL;
+ };
+ if( ! ret )
+ {
+ exception_from_error_queue(crypto_Error);
+ BIO_free(bio);
+ return NULL;
+ }
+ buf_len = BIO_get_mem_data(bio, &temp);
+ buffer = PyString_FromStringAndSize(temp, buf_len);
+ BIO_free(bio);
+ return buffer;
+}
+
+crypto_CRLObj *
+crypto_CRL_New(X509_CRL *crl)
+{
+ crypto_CRLObj *self;
+
+ self = PyObject_New(crypto_CRLObj, &crypto_CRL_Type);
+ if (self==NULL)
+ return NULL;
+ self->crl = crl;
+ return self;
+}
+
+/*
+ * ADD_METHOD(name) expands to a correct PyMethodDef declaration
+ * { 'name', (PyCFunction)crypto_CRL_name, METH_VARARGS, crypto_CRL_name_doc }
+ * for convenience
+ */
+#define ADD_METHOD(name) \
+ { #name, (PyCFunction)crypto_CRL_##name, METH_VARARGS, crypto_CRL_##name##_doc }
+#define ADD_KW_METHOD(name) \
+ { #name, (PyCFunction)crypto_CRL_##name, METH_VARARGS | METH_KEYWORDS, crypto_CRL_##name##_doc }
+static PyMethodDef crypto_CRL_methods[] =
+{
+ ADD_KW_METHOD(add_revoked),
+ ADD_METHOD(get_revoked),
+ ADD_KW_METHOD(export),
+ { NULL, NULL }
+};
+#undef ADD_METHOD
+
+
+static PyObject *
+crypto_CRL_getattr(crypto_CRLObj *self, char *name)
+{
+ return Py_FindMethod(crypto_CRL_methods, (PyObject *)self, name);
+}
+
+static void
+crypto_CRL_dealloc(crypto_CRLObj *self)
+{
+ X509_CRL_free(self->crl);
+ self->crl = NULL;
+
+ PyObject_Del(self);
+}
+
+static char crypto_CRL_doc[] = "\n\
+CRL() -> CRL instance\n\
+\n\
+Create a new empty CRL object.\n\
+\n\
+@returns: The CRL object\n\
+";
+
+static PyObject* crypto_CRL_new(PyTypeObject *subtype, PyObject *args, PyObject *kwargs) {
+ if (!PyArg_ParseTuple(args, ":CRL")) {
+ return NULL;
+ }
+
+ return (PyObject *)crypto_CRL_New(X509_CRL_new());
+}
+
+PyTypeObject crypto_CRL_Type = {
+ PyObject_HEAD_INIT(NULL)
+ 0,
+ "CRL",
+ sizeof(crypto_CRLObj),
+ 0,
+ (destructor)crypto_CRL_dealloc,
+ NULL, /* print */
+ (getattrfunc)crypto_CRL_getattr,
+ NULL, /* setattr */
+ NULL, /* compare */
+ NULL, /* repr */
+ NULL, /* as_number */
+ NULL, /* as_sequence */
+ NULL, /* as_mapping */
+ NULL, /* hash */
+ NULL, /* call */
+ NULL, /* str */
+ NULL, /* getattro */
+ NULL, /* setattro */
+ NULL, /* as_buffer */
+ Py_TPFLAGS_DEFAULT,
+ crypto_CRL_doc, /* doc */
+ NULL, /* traverse */
+ NULL, /* clear */
+ NULL, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ NULL, /* tp_iter */
+ NULL, /* tp_iternext */
+ crypto_CRL_methods, /* tp_methods */
+ NULL, /* tp_members */
+ NULL, /* tp_getset */
+ NULL, /* tp_base */
+ NULL, /* tp_dict */
+ NULL, /* tp_descr_get */
+ NULL, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ NULL, /* tp_init */
+ NULL, /* tp_alloc */
+ crypto_CRL_new, /* tp_new */
+};
+
+int init_crypto_crl(PyObject *module) {
+ if(PyType_Ready(&crypto_CRL_Type) < 0) {
+ return 0;
+ }
+
+ if (PyModule_AddObject(module, "CRL", (PyObject *)&crypto_CRL_Type) != 0) {
+ return 0;
+ }
+ return 1;
+}
+
diff --git a/src/crypto/crl.h b/src/crypto/crl.h
new file mode 100644
index 0000000..87f5048
--- /dev/null
+++ b/src/crypto/crl.h
@@ -0,0 +1,19 @@
+#ifndef PyOpenSSL_crypto_CRL_H_
+#define PyOpenSSL_crypto_CRL_H_
+
+#include <Python.h>
+
+extern int init_crypto_crl (PyObject *);
+
+extern PyTypeObject crypto_CRL_Type;
+
+#define crypto_CRL_Check(v) ((v)->ob_type == &crypto_CRL_Type)
+
+typedef struct {
+ PyObject_HEAD
+ X509_CRL *crl;
+} crypto_CRLObj;
+
+crypto_CRLObj * crypto_CRL_New(X509_CRL *crl);
+
+#endif
diff --git a/src/crypto/crypto.c b/src/crypto/crypto.c
index 21501c1..a3ce9a9 100644
--- a/src/crypto/crypto.c
+++ b/src/crypto/crypto.c
@@ -430,6 +430,53 @@
return buffer;
}
+static char crypto_load_crl_doc[] = "\n\
+Load a certificate revocation list from a buffer\n\
+\n\
+@param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1)\n\
+@param buffer: The buffer the CRL is stored in\n\
+\n\
+@return: The PKey object\n\
+";
+
+static PyObject *
+crypto_load_crl(PyObject *spam, PyObject *args)
+{
+ int type, len;
+ char *buffer;
+ BIO *bio;
+ X509_CRL *crl;
+
+ if (!PyArg_ParseTuple(args, "is#:load_crl", &type, &buffer, &len))
+ return NULL;
+
+ bio = BIO_new_mem_buf(buffer, len);
+ switch (type)
+ {
+ case X509_FILETYPE_PEM:
+ crl = PEM_read_bio_X509_CRL(bio, NULL, NULL, NULL);
+ break;
+
+ case X509_FILETYPE_ASN1:
+ crl = d2i_X509_CRL_bio(bio, NULL);
+ break;
+
+ default:
+ PyErr_SetString(PyExc_ValueError, "type argument must be FILETYPE_PEM or FILETYPE_ASN1");
+ BIO_free(bio);
+ return NULL;
+ }
+ BIO_free(bio);
+
+ if (crl == NULL)
+ {
+ exception_from_error_queue(crypto_Error);
+ return NULL;
+ }
+
+ return (PyObject *)crypto_CRL_New(crl);
+}
+
static char crypto_load_pkcs7_data_doc[] = "\n\
Load pkcs7 data from a buffer\n\
\n\
@@ -555,6 +602,7 @@
{ "dump_certificate", (PyCFunction)crypto_dump_certificate, METH_VARARGS, crypto_dump_certificate_doc },
{ "load_certificate_request", (PyCFunction)crypto_load_certificate_request, METH_VARARGS, crypto_load_certificate_request_doc },
{ "dump_certificate_request", (PyCFunction)crypto_dump_certificate_request, METH_VARARGS, crypto_dump_certificate_request_doc },
+ { "load_crl", (PyCFunction)crypto_load_crl, METH_VARARGS, crypto_load_crl_doc },
{ "load_pkcs7_data", (PyCFunction)crypto_load_pkcs7_data, METH_VARARGS, crypto_load_pkcs7_data_doc },
{ "load_pkcs12", (PyCFunction)crypto_load_pkcs12, METH_VARARGS, crypto_load_pkcs12_doc },
{ "X509_verify_cert_error_string", (PyCFunction)crypto_X509_verify_cert_error_string, METH_VARARGS, crypto_X509_verify_cert_error_string_doc },
@@ -696,7 +744,10 @@
goto error;
if (!init_crypto_netscape_spki(module))
goto error;
-
+ if (!init_crypto_crl(module))
+ goto error;
+ if (!init_crypto_revoked(module))
+ goto error;
error:
;
}
diff --git a/src/crypto/crypto.h b/src/crypto/crypto.h
index b5e6b65..057b5dd 100644
--- a/src/crypto/crypto.h
+++ b/src/crypto/crypto.h
@@ -23,6 +23,8 @@
#include "x509ext.h"
#include "pkcs7.h"
#include "pkcs12.h"
+#include "crl.h"
+#include "revoked.h"
#include "../util.h"
extern PyObject *crypto_Error;
diff --git a/src/crypto/revoked.c b/src/crypto/revoked.c
new file mode 100644
index 0000000..8289408
--- /dev/null
+++ b/src/crypto/revoked.c
@@ -0,0 +1,258 @@
+#include <Python.h>
+#define crypto_MODULE
+#include "crypto.h"
+
+
+static char crypto_Revoked_get_rev_date_doc[] = "\n\
+Retrieve the revocation date\n\
+\n\
+@return: A string giving the timestamp, in the format:\n\
+\n\
+ YYYYMMDDhhmmssZ\n\
+ YYYYMMDDhhmmss+hhmm\n\
+ YYYYMMDDhhmmss-hhmm\n\
+";
+
+static PyObject*
+crypto_Revoked_get_rev_date(crypto_RevokedObj *self, PyObject *args)
+{
+ /* returns a borrowed reference. */
+ return _get_asn1_time(
+ ":get_rev_date", self->revoked->revocationDate, args);
+}
+
+static char crypto_Revoked_set_rev_date_doc[] = "\n\
+Set the revocation timestamp\n\
+\n\
+@param when: A string giving the timestamp, in the format:\n\
+\n\
+ YYYYMMDDhhmmssZ\n\
+ YYYYMMDDhhmmss+hhmm\n\
+ YYYYMMDDhhmmss-hhmm\n\
+\n\
+@return: None\n\
+";
+
+static PyObject*
+crypto_Revoked_set_rev_date(crypto_RevokedObj *self, PyObject *args)
+{
+ return _set_asn1_time(
+ "s:set_rev_date", self->revoked->revocationDate, args);
+}
+
+
+static PyObject *
+ASN1_INTEGER_to_PyString(ASN1_INTEGER *asn1_int)
+{
+ BIO *bio = NULL;
+ PyObject *buf = NULL;
+ int ret, pending;
+
+ /* Create a openssl BIO buffer */
+ bio = BIO_new(BIO_s_mem());
+ if (bio == NULL)
+ goto err;
+
+ /* Write the integer to the BIO as a hex string. */
+ i2a_ASN1_INTEGER(bio, asn1_int);
+
+ /* Allocate a Python string. */
+ pending = BIO_pending(bio);
+ buf = PyString_FromStringAndSize(NULL, pending);
+ if (buf == NULL) {
+ goto err;
+ }
+
+ /* Copy the BIO contents to a Python string. */
+ ret = BIO_read(bio, PyString_AsString(buf), pending);
+ if (ret <= 0) { /* problem with BIO_read */
+ goto err;
+ }
+
+ /* Cleanup */
+ BIO_free(bio);
+ bio = NULL;
+ return buf;
+
+ err:
+ if(bio) {
+ BIO_free(bio);
+ }
+ if(buf) {
+ Py_DECREF(buf);
+ }
+ return NULL;
+}
+
+
+static char crypto_Revoked_get_serial_doc[] = "\n\
+Return the serial number of a Revoked structure\n\
+\n\
+@return: The serial number as a string\n\
+";
+static PyObject *
+crypto_Revoked_get_serial(crypto_RevokedObj *self, PyObject *args)
+{
+ if (!PyArg_ParseTuple(args, ":get_serial"))
+ return NULL;
+
+ if(self->revoked->serialNumber == NULL) {
+ /* never happens */
+ Py_INCREF(Py_None);
+ return Py_None;
+ } else {
+ return ASN1_INTEGER_to_PyString(self->revoked->serialNumber);
+ }
+}
+
+static char crypto_Revoked_set_serial_doc[] = "\n\
+Set the serial number of a revoked Revoked structure\n\
+\n\
+@param hex_str: The new serial number.\n\
+@type hex_str: L{str}\n\
+@return: None\n\
+";
+static PyObject *
+crypto_Revoked_set_serial(crypto_RevokedObj *self, PyObject *args, PyObject *keywds)
+{
+ static char *kwlist[] = {"hex_str", NULL};
+ const char *hex_str = NULL;
+ BIGNUM *serial = NULL;
+ ASN1_INTEGER *tmpser = NULL;
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywds, "s:set_serial",
+ kwlist, &hex_str))
+ return NULL;
+
+ if( ! BN_hex2bn(&serial, hex_str) ) {
+ PyErr_SetString(PyExc_TypeError, "bad hex string");
+ return NULL;
+ }
+
+ tmpser = BN_to_ASN1_INTEGER(serial, NULL);
+ BN_free(serial);
+ serial = NULL;
+ X509_REVOKED_set_serialNumber(self->revoked, tmpser);
+ ASN1_INTEGER_free(tmpser);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+
+crypto_RevokedObj *
+crypto_Revoked_New(X509_REVOKED *revoked)
+{
+ crypto_RevokedObj *self;
+
+ self = PyObject_New(crypto_RevokedObj, &crypto_Revoked_Type);
+ if (self==NULL)
+ return NULL;
+ self->revoked = revoked;
+ return self;
+}
+
+/*
+ * ADD_METHOD(name) expands to a correct PyMethodDef declaration
+ * { 'name', (PyCFunction)crypto_Revoked_name, METH_VARARGS, crypto_Revoked_name_doc }
+ * for convenience
+ */
+#define ADD_METHOD(name) \
+ { #name, (PyCFunction)crypto_Revoked_##name, METH_VARARGS, crypto_Revoked_##name##_doc }
+#define ADD_KW_METHOD(name) \
+ { #name, (PyCFunction)crypto_Revoked_##name, METH_VARARGS | METH_KEYWORDS, crypto_Revoked_##name##_doc }
+static PyMethodDef crypto_Revoked_methods[] =
+{
+ ADD_METHOD(get_rev_date),
+ ADD_METHOD(set_rev_date),
+ ADD_METHOD(get_serial),
+ ADD_KW_METHOD(set_serial),
+ { NULL, NULL }
+};
+#undef ADD_METHOD
+
+
+static PyObject *
+crypto_Revoked_getattr(crypto_RevokedObj *self, char *name)
+{
+ return Py_FindMethod(crypto_Revoked_methods, (PyObject *)self, name);
+}
+
+static void
+crypto_Revoked_dealloc(crypto_RevokedObj *self)
+{
+ X509_REVOKED_free(self->revoked);
+ self->revoked = NULL;
+
+ PyObject_Del(self);
+}
+
+static char crypto_Revoked_doc[] = "\n\
+Revoked() -> Revoked instance\n\
+\n\
+Create a new empty Revoked object.\n\
+\n\
+@returns: The Revoked object\n\
+";
+
+static PyObject* crypto_Revoked_new(PyTypeObject *subtype, PyObject *args, PyObject *kwargs) {
+ if (!PyArg_ParseTuple(args, ":Revoked")) {
+ return NULL;
+ }
+
+ return (PyObject *)crypto_Revoked_New(X509_REVOKED_new());
+}
+
+PyTypeObject crypto_Revoked_Type = {
+ PyObject_HEAD_INIT(NULL)
+ 0,
+ "Revoked",
+ sizeof(crypto_RevokedObj),
+ 0,
+ (destructor)crypto_Revoked_dealloc,
+ NULL, /* print */
+ (getattrfunc)crypto_Revoked_getattr,
+ NULL, /* setattr */
+ NULL, /* compare */
+ NULL, /* repr */
+ NULL, /* as_number */
+ NULL, /* as_sequence */
+ NULL, /* as_mapping */
+ NULL, /* hash */
+ NULL, /* call */
+ NULL, /* str */
+ NULL, /* getattro */
+ NULL, /* setattro */
+ NULL, /* as_buffer */
+ Py_TPFLAGS_DEFAULT,
+ crypto_Revoked_doc, /* doc */
+ NULL, /* traverse */
+ NULL, /* clear */
+ NULL, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ NULL, /* tp_iter */
+ NULL, /* tp_iternext */
+ crypto_Revoked_methods, /* tp_methods */
+ NULL, /* tp_members */
+ NULL, /* tp_getset */
+ NULL, /* tp_base */
+ NULL, /* tp_dict */
+ NULL, /* tp_descr_get */
+ NULL, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ NULL, /* tp_init */
+ NULL, /* tp_alloc */
+ crypto_Revoked_new, /* tp_new */
+};
+
+int init_crypto_revoked(PyObject *module) {
+ if(PyType_Ready(&crypto_Revoked_Type) < 0) {
+ return 0;
+ }
+
+ if (PyModule_AddObject(module, "Revoked", (PyObject *)&crypto_Revoked_Type) != 0) {
+ return 0;
+ }
+ return 1;
+}
+
diff --git a/src/crypto/revoked.h b/src/crypto/revoked.h
new file mode 100644
index 0000000..fb85ac6
--- /dev/null
+++ b/src/crypto/revoked.h
@@ -0,0 +1,18 @@
+#ifndef PyOpenSSL_crypto_REVOKED_H_
+#define PyOpenSSL_crypto_REVOKED_H_
+
+#include <Python.h>
+
+extern PyTypeObject crypto_Revoked_Type;
+
+#define crypto_Revoked_Check(v) ((v)->ob_type == &crypto_Revoked_Type)
+
+typedef struct {
+ PyObject_HEAD
+ X509_REVOKED *revoked;
+} crypto_RevokedObj;
+
+extern int init_crypto_revoked (PyObject *);
+extern crypto_RevokedObj * crypto_Revoked_New(X509_REVOKED *revoked);
+
+#endif
diff --git a/src/crypto/x509.c b/src/crypto/x509.c
index e089d40..daae351 100644
--- a/src/crypto/x509.c
+++ b/src/crypto/x509.c
@@ -335,8 +335,8 @@
return Py_None;
}
-static PyObject*
-_set_asn1_time(char *format, ASN1_TIME* timestamp, crypto_X509Obj *self, PyObject *args)
+PyObject*
+_set_asn1_time(char *format, ASN1_TIME* timestamp, PyObject *args)
{
char *when;
@@ -375,7 +375,7 @@
crypto_X509_set_notBefore(crypto_X509Obj *self, PyObject *args)
{
return _set_asn1_time(
- "s:set_notBefore", X509_get_notBefore(self->x509), self, args);
+ "s:set_notBefore", X509_get_notBefore(self->x509), args);
}
static char crypto_X509_set_notAfter_doc[] = "\n\
@@ -394,11 +394,11 @@
crypto_X509_set_notAfter(crypto_X509Obj *self, PyObject *args)
{
return _set_asn1_time(
- "s:set_notAfter", X509_get_notAfter(self->x509), self, args);
+ "s:set_notAfter", X509_get_notAfter(self->x509), args);
}
-static PyObject*
-_get_asn1_time(char *format, ASN1_TIME* timestamp, crypto_X509Obj *self, PyObject *args)
+PyObject*
+_get_asn1_time(char *format, ASN1_TIME* timestamp, PyObject *args)
{
ASN1_GENERALIZEDTIME *gt_timestamp = NULL;
PyObject *py_timestamp = NULL;
@@ -450,7 +450,7 @@
* X509_get_notBefore returns a borrowed reference.
*/
return _get_asn1_time(
- ":get_notBefore", X509_get_notBefore(self->x509), self, args);
+ ":get_notBefore", X509_get_notBefore(self->x509), args);
}
@@ -472,7 +472,7 @@
* X509_get_notAfter returns a borrowed reference.
*/
return _get_asn1_time(
- ":get_notAfter", X509_get_notAfter(self->x509), self, args);
+ ":get_notAfter", X509_get_notAfter(self->x509), args);
}
diff --git a/src/crypto/x509.h b/src/crypto/x509.h
index 40768cf..43e41eb 100644
--- a/src/crypto/x509.h
+++ b/src/crypto/x509.h
@@ -16,8 +16,6 @@
#include <Python.h>
#include <openssl/ssl.h>
-extern int init_crypto_x509 (PyObject *);
-
extern PyTypeObject crypto_X509_Type;
#define crypto_X509_Check(v) ((v)->ob_type == &crypto_X509_Type)
@@ -28,5 +26,9 @@
int dealloc;
} crypto_X509Obj;
+PyObject* _set_asn1_time(char *format, ASN1_TIME* timestamp, PyObject *args);
+PyObject* _get_asn1_time(char *format, ASN1_TIME* timestamp, PyObject *args);
+extern int init_crypto_x509 (PyObject *);
+
#endif
diff --git a/test/test_crypto.py b/test/test_crypto.py
index fce2441..630ce01 100644
--- a/test/test_crypto.py
+++ b/test/test_crypto.py
@@ -19,6 +19,7 @@
from OpenSSL.crypto import dump_certificate_request, dump_privatekey
from OpenSSL.crypto import PKCS7Type, load_pkcs7_data
from OpenSSL.crypto import PKCS12Type, load_pkcs12
+from OpenSSL.crypto import CRL, Revoked, load_crl
from OpenSSL.crypto import NetscapeSPKI, NetscapeSPKIType
from OpenSSL.test.util import TestCase
@@ -1142,6 +1143,177 @@
self.assertTrue(isinstance(nspki, NetscapeSPKIType))
+def _runopenssl(pem, *args):
+ """
+ Run the command line openssl tool with the given arguments and write
+ the given PEM to its stdin.
+ """
+ write, read = popen2(" ".join(("openssl",) + args), "b")
+ write.write(pem)
+ write.close()
+ return read.read()
+
+
+class RevokedTests(TestCase):
+ """
+ Tests for L{OpenSSL.crypto.Revoked}
+ """
+ def test_construction(self):
+ """
+ Confirm we can create L{OpenSSL.crypto.Revoked}. Check
+ that it is empty.
+ """
+ revoked = Revoked()
+ self.assertTrue( isinstance(revoked, Revoked) )
+ self.assertEqual( type(revoked), Revoked )
+ self.assertEqual( revoked.get_serial(), '00' )
+ self.assertEqual( revoked.get_rev_date(), None )
+
+
+ def test_serial(self):
+ """
+ Confirm we can set and get serial numbers from
+ L{OpenSSL.crypto.Revoked}. Confirm errors are handled
+ with grace.
+ """
+ revoked = Revoked()
+ ret = revoked.set_serial('10b')
+ self.assertEqual( ret, None )
+ ser = revoked.get_serial()
+ self.assertEqual( ser, '010B' )
+
+ revoked.set_serial('31ppp') # a type error would be nice
+ ser = revoked.get_serial()
+ self.assertEqual( ser, '31' )
+
+ self.assertRaises(TypeError, revoked.set_serial, 'pqrst')
+ self.assertRaises(TypeError, revoked.set_serial, 100)
+
+
+ def test_date(self):
+ """
+ Confirm we can set and get revocation dates from
+ L{OpenSSL.crypto.Revoked}. Confirm errors are handled
+ with grace.
+ """
+ revoked = Revoked()
+ date = revoked.get_rev_date()
+ self.assertEqual( date, None )
+
+ now = datetime.now().strftime("%Y%m%d%H%M%SZ")
+ ret = revoked.set_rev_date(now)
+ self.assertEqual( ret, None )
+ date = revoked.get_rev_date()
+ self.assertEqual( date, now )
+
+
+
+class CRLTests(TestCase):
+ """
+ Tests for L{OpenSSL.crypto.CRL}
+ """
+ cert = load_certificate(FILETYPE_PEM, cleartextCertificatePEM)
+ pkey = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM)
+
+ def test_construction(self):
+ """
+ Confirm we can create L{OpenSSL.crypto.CRL}. Check
+ that it is empty
+ """
+ crl = CRL()
+ self.assertTrue( isinstance(crl, CRL) )
+ self.assertEqual(crl.get_revoked(), None)
+
+
+ def test_export(self):
+ """
+ Use python to create a simple CRL with a revocation, and export
+ the CRL in formats of PEM, DER and text. Those outputs are verified
+ with the openssl program.
+ """
+ crl = CRL()
+ revoked = Revoked()
+ now = datetime.now().strftime("%Y%m%d%H%M%SZ")
+ revoked.set_rev_date(now)
+ revoked.set_serial('3ab')
+ crl.add_revoked(revoked)
+
+ # PEM format
+ dumped_crl = crl.export(self.cert, self.pkey, days=20)
+ text = _runopenssl(dumped_crl, "crl", "-noout", "-text")
+ text.index('Serial Number: 03AB')
+ text.index('Issuer: /C=US/ST=IL/L=Chicago/O=Testing/CN=Testing Root CA')
+
+ # DER format
+ dumped_crl = crl.export(self.cert, self.pkey, FILETYPE_ASN1)
+ text = _runopenssl(dumped_crl, "crl", "-noout", "-text", "-inform", "DER")
+ text.index('Serial Number: 03AB')
+ text.index('Issuer: /C=US/ST=IL/L=Chicago/O=Testing/CN=Testing Root CA')
+
+ # text format
+ dumped_text = crl.export(self.cert, self.pkey, type=FILETYPE_TEXT)
+ self.assertEqual(text, dumped_text)
+
+
+ def test_get_revoked(self):
+ """
+ Use python to create a simple CRL with two revocations.
+ Get back the L{Revoked} using L{OpenSSL.CRL.get_revoked} and
+ verify them.
+ """
+ crl = CRL()
+
+ revoked = Revoked()
+ now = datetime.now().strftime("%Y%m%d%H%M%SZ")
+ revoked.set_rev_date(now)
+ revoked.set_serial('3ab')
+ crl.add_revoked(revoked)
+ revoked.set_serial('100')
+ crl.add_revoked(revoked)
+
+ revs = crl.get_revoked()
+ self.assertEqual(len(revs), 2)
+ self.assertEqual(type(revs[0]), Revoked)
+ self.assertEqual(type(revs[1]), Revoked)
+ self.assertEqual(revs[0].get_serial(), '03AB')
+ self.assertEqual(revs[1].get_serial(), '0100')
+ self.assertEqual(revs[0].get_rev_date(), now)
+ self.assertEqual(revs[1].get_rev_date(), now)
+
+
+ def test_load_crl(self):
+ """
+ Load a known CRL and inspect its revocations. Both
+ PEM and DER formats are loaded.
+ """
+
+ crl_txt = """
+-----BEGIN X509 CRL-----
+MIIBTTCBtzANBgkqhkiG9w0BAQQFADBYMQswCQYDVQQGEwJVUzELMAkGA1UECBMC
+SUwxEDAOBgNVBAcTB0NoaWNhZ28xEDAOBgNVBAoTB1Rlc3RpbmcxGDAWBgNVBAMT
+D1Rlc3RpbmcgUm9vdCBDQRcNMDkwNzI1MDIxMjE0WhcNMDkxMTAyMDIxMjE0WjAu
+MBUCAgOrGA8yMDA5MDcyNDIxMTIxNFowFQICAQAYDzIwMDkwNzI0MjExMjE0WjAN
+BgkqhkiG9w0BAQQFAAOBgQApflU91pdbbSXNMLxRHAwz+2M2vzhmpFDYsX8gPe76
+GgrEY475v1CGJTdmKQnwosUx1tJ6HgoueAfTvzLGgVhqfeeR6BTjhnJH69rW+L6A
+w47xSB7rmUglsn3HlAdZl4tIex+SlH7AB1mEsWNJ0VA0mDEF01eOaBwBfEmK3zGd
+ng==
+-----END X509 CRL-----
+"""
+ crl = load_crl(FILETYPE_PEM, crl_txt)
+ revs = crl.get_revoked()
+ self.assertEqual(len(revs), 2)
+ self.assertEqual(revs[0].get_serial(), '03AB')
+ self.assertEqual(revs[1].get_serial(), '0100')
+
+ der = _runopenssl(crl_txt, "crl", "-outform", "DER")
+ crl = load_crl(FILETYPE_ASN1, der)
+ revs = crl.get_revoked()
+ self.assertEqual(len(revs), 2)
+ self.assertEqual(revs[0].get_serial(), '03AB')
+ self.assertEqual(revs[1].get_serial(), '0100')
+
+
+
if __name__ == '__main__':
main()