merge trunk
diff --git a/doc/pyOpenSSL.tex b/doc/pyOpenSSL.tex
index d772c25..a41b575 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}
@@ -612,6 +626,60 @@
 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]{all_reasons}{}
+Return a list of all supported reasons.
+\end{methoddesc}
+
+\begin{methoddesc}[Revoked]{get_reason}{}
+Return the revocation reason as a str.  Can be
+None, which differs from "Unspecified".
+\end{methoddesc}
+
+\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_reason}{reason}
+Set the revocation reason.  \var{reason} must
+be None or a string, but the values are limited.  
+Spaces and case are ignored.  See \method{all_reasons}.
+\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 af02aae..9cb4364 100755
--- a/setup.py
+++ b/setup.py
@@ -26,12 +26,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..8e1c135
--- /dev/null
+++ b/src/crypto/crl.c
@@ -0,0 +1,290 @@
+#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(cert, key[, type[, days]]) -> 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, FILETYPE_ASN1, or FILETYPE_TEXT");
+            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 dd279cf..df48a65 100644
--- a/src/crypto/crypto.c
+++ b/src/crypto/crypto.c
@@ -432,6 +432,51 @@
     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\
@@ -651,6 +696,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 },
     { "sign", (PyCFunction)crypto_sign, METH_VARARGS, crypto_sign_doc },
@@ -794,7 +840,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..3042c4d
--- /dev/null
+++ b/src/crypto/revoked.c
@@ -0,0 +1,445 @@
+#include <Python.h>
+#define crypto_MODULE
+#include "crypto.h"
+
+#ifdef _WIN32
+#define strcasecmp(string1, string2) _stricmp(string1, string2)
+#endif
+
+/* http://www.openssl.org/docs/apps/x509v3_config.html#CRL_distribution_points_ */
+/* which differs from crl_reasons of crypto/x509v3/v3_enum.c that matches */
+/* OCSP_crl_reason_str.  We use the latter, just like the command line program.  */
+static const char *crl_reasons[] = {
+    "unspecified",
+    "keyCompromise",
+    "CACompromise",
+    "affiliationChanged",
+    "superseded",
+    "cessationOfOperation",
+    "certificateHold",
+    NULL,
+    "removeFromCRL",
+};
+
+#define NUM_REASONS (sizeof(crl_reasons) / sizeof(char *))
+
+static char crypto_Revoked_all_reasons_doc[] = "\n\
+Return a list of all the supported reason strings.\n\
+\n\
+@return: A list of reason strings.\n\
+";
+static PyObject *
+crypto_Revoked_all_reasons(crypto_RevokedObj *self, PyObject *args) {
+    PyObject *list, *str;
+    int j;
+
+    list = PyList_New(0);
+    for (j = 0; j < NUM_REASONS; j++) {
+        if(crl_reasons[j]) {
+            str = PyString_FromString(crl_reasons[j]);
+            PyList_Append(list, str);
+            Py_DECREF(str);
+        }
+    }
+    return list;
+}
+
+static PyObject *
+X509_EXTENSION_value_to_PyString(X509_EXTENSION *ex) {
+    BIO *bio = NULL;
+    PyObject *str = NULL;
+    int str_len;
+    char *tmp_str;
+
+    /* Create a openssl BIO buffer */
+    bio = BIO_new(BIO_s_mem());
+    if (bio == NULL) {
+        goto err;
+    }
+
+    /* These are not the droids you are looking for. */
+    if (!X509V3_EXT_print(bio, ex, 0, 0)) {
+        if (M_ASN1_OCTET_STRING_print(bio, ex->value) == 0) {
+            goto err;
+        }
+    }
+
+    /* Convert to a Python string. */
+    str_len = BIO_get_mem_data(bio, &tmp_str);
+    str = PyString_FromStringAndSize(tmp_str, str_len);
+
+    /* Cleanup */
+    BIO_free(bio);
+    return str;
+
+ err:
+    if (bio) {
+        BIO_free(bio);
+    }
+    if (str) {
+        Py_DECREF(str);
+    }
+    return NULL;
+}
+
+static void
+delete_reason(STACK_OF(X509_EXTENSION) *sk) {
+    X509_EXTENSION * ext;
+    int j;
+
+    for (j = 0; j < sk_X509_EXTENSION_num(sk); j++) {
+         ext = sk_X509_EXTENSION_value(sk, j);
+         if (OBJ_obj2nid(ext->object) == NID_crl_reason) {
+             X509_EXTENSION_free(ext);
+             (void) sk_X509_EXTENSION_delete(sk, j);
+             break;
+         }
+    }
+}
+
+static int
+reason_str_to_code(const char * reason_str) {
+    int reason_code = -1, j;
+    char *spaceless_reason, * sp;
+
+    /*  Remove spaces so that the responses of
+     *  get_reason() work in set_reason()  */
+    if ((spaceless_reason = strdup(reason_str)) == NULL) {
+        return -1;
+    }
+
+    while ((sp = strchr(spaceless_reason, ' '))) {
+       memmove(sp, sp+1, strlen(sp));
+    }
+
+    for (j = 0; j < NUM_REASONS; j++) {
+        if(crl_reasons[j] && !strcasecmp(spaceless_reason, crl_reasons[j])) {
+            reason_code = j;
+            break;
+        }
+    }
+    free(spaceless_reason);
+    return reason_code;
+}
+
+static char crypto_Revoked_set_reason_doc[] = "\n\
+Set the reason of a Revoked object.\n\
+\n\
+@param reason: The reason string.\n\
+@type reason: L{str}\n\
+@return: None\n\
+";
+static PyObject *
+crypto_Revoked_set_reason(crypto_RevokedObj *self, PyObject *args, PyObject *keywds) {
+    static char *kwlist[] = {"reason", NULL};
+    const char *reason_str = NULL;
+    int reason_code;
+    ASN1_ENUMERATED *rtmp = NULL;
+
+    if (!PyArg_ParseTupleAndKeywords(args, keywds, "z:set_reason",
+                                     kwlist, &reason_str)) {
+        return NULL;
+    }
+
+    if(reason_str == NULL) {
+        delete_reason(self->revoked->extensions);
+        goto done;
+    }
+
+    reason_code = reason_str_to_code(reason_str);
+    if (reason_code == -1) {
+        PyErr_SetString(PyExc_ValueError, "bad reason string");
+        return NULL;
+    }
+
+    rtmp = ASN1_ENUMERATED_new();
+    if (!rtmp || !ASN1_ENUMERATED_set(rtmp, reason_code)) {
+        goto err;
+    }
+    delete_reason(self->revoked->extensions);
+    if (!X509_REVOKED_add1_ext_i2d(self->revoked, NID_crl_reason, rtmp, 0, 0)) {
+        goto err;
+    }
+
+ done:
+    Py_INCREF(Py_None);
+    return Py_None;
+
+ err:
+    exception_from_error_queue(crypto_Error);
+    return NULL;
+}
+
+
+static char crypto_Revoked_get_reason_doc[] = "\n\
+Return the reason of a Revoked object.\n\
+\n\
+@return: The reason as a string\n\
+";
+static PyObject *
+crypto_Revoked_get_reason(crypto_RevokedObj *self, PyObject *args) {
+    X509_EXTENSION * ext;
+    int j;
+    STACK_OF(X509_EXTENSION) *sk = NULL;
+
+    if (!PyArg_ParseTuple(args, ":get_reason")) {
+        return NULL;
+    }
+
+    sk = self->revoked->extensions;
+    for (j = 0; j < sk_X509_EXTENSION_num(sk); j++) {
+         ext = sk_X509_EXTENSION_value(sk, j);
+         if (OBJ_obj2nid(ext->object) == NID_crl_reason) {
+             return X509_EXTENSION_value_to_PyString(ext);
+         }
+    }
+
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+
+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);
+}
+
+/* The integer is converted to an upper-case hex string
+ * without a '0x' prefix. */
+static PyObject *
+ASN1_INTEGER_to_PyString(ASN1_INTEGER *asn1_int) {
+    BIO *bio = NULL;
+    PyObject *str = NULL;
+    int str_len;
+    char *tmp_str;
+
+    /* 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. */
+    if (i2a_ASN1_INTEGER(bio, asn1_int) < 0) {
+        goto err;
+    }
+
+    /* Convert to a Python string. */
+    str_len = BIO_get_mem_data(bio, &tmp_str);
+    str = PyString_FromStringAndSize(tmp_str, str_len);
+
+    /* Cleanup */
+    BIO_free(bio);
+    return str;
+
+ err:
+    if (bio) {
+        BIO_free(bio);
+    }
+    if (str) {
+        Py_DECREF(str);
+    }
+    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_ValueError, "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(all_reasons),
+    ADD_METHOD(get_reason),
+    ADD_KW_METHOD(set_reason),
+    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 77a0c85..ae99fc5 100644
--- a/test/test_crypto.py
+++ b/test/test_crypto.py
@@ -19,7 +19,8 @@
 from OpenSSL.crypto import dump_certificate, load_certificate_request
 from OpenSSL.crypto import dump_certificate_request, dump_privatekey
 from OpenSSL.crypto import PKCS7Type, load_pkcs7_data
-from OpenSSL.crypto import PKCS12Type, load_pkcs12, PKCS12
+from OpenSSL.crypto import PKCS12, PKCS12Type, load_pkcs12
+from OpenSSL.crypto import CRL, Revoked, load_crl
 from OpenSSL.crypto import NetscapeSPKI, NetscapeSPKIType
 from OpenSSL.test.util import TestCase
 
@@ -226,6 +227,18 @@
 -----END PKCS7-----
 """
 
+crlData ="""\
+-----BEGIN X509 CRL-----
+MIIBWzCBxTANBgkqhkiG9w0BAQQFADBYMQswCQYDVQQGEwJVUzELMAkGA1UECBMC
+SUwxEDAOBgNVBAcTB0NoaWNhZ28xEDAOBgNVBAoTB1Rlc3RpbmcxGDAWBgNVBAMT
+D1Rlc3RpbmcgUm9vdCBDQRcNMDkwNzI2MDQzNDU2WhcNMTIwOTI3MDI0MTUyWjA8
+MBUCAgOrGA8yMDA5MDcyNTIzMzQ1NlowIwICAQAYDzIwMDkwNzI1MjMzNDU2WjAM
+MAoGA1UdFQQDCgEEMA0GCSqGSIb3DQEBBAUAA4GBAEBt7xTs2htdD3d4ErrcGAw1
+4dKcVnIWTutoI7xxen26Wwvh8VCsT7i/UeP+rBl9rC/kfjWjzQk3/zleaarGTpBT
+0yp4HXRFFoRhhSE/hP+eteaPXRgrsNRLHe9ZDd69wmh7J1wMDb0m81RG7kqcbsid
+vrzEeLDRiiPl92dyyWmu
+-----END X509 CRL-----
+"""
 
 class X509ExtTests(TestCase):
     """
@@ -916,6 +929,11 @@
         # An invalid string results in a ValueError
         self.assertRaises(ValueError, set, "foo bar")
 
+        # The wrong number of arguments results in a TypeError.
+        self.assertRaises(TypeError, set)
+        self.assertRaises(TypeError, set, "20040203040506Z", "20040203040506Z")
+        self.assertRaises(TypeError, get, "foo bar")
+
 
     def test_set_notBefore(self):
         """
@@ -1324,6 +1342,7 @@
     return ' '.join(map(cmdLineQuote, arguments))
 
 
+
 def _runopenssl(pem, *args):
     """
     Run the command line openssl tool with the given arguments and write
@@ -1532,6 +1551,313 @@
         self.assertTrue(isinstance(nspki, NetscapeSPKIType))
 
 
+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 )
+        self.assertEqual( revoked.get_reason(), None )
+
+
+    def test_construction_wrong_args(self):
+        """
+        Calling L{OpenSSL.crypto.Revoked} with any arguments results
+        in a L{TypeError} being raised.
+        """
+        self.assertRaises(TypeError, Revoked, None)
+        self.assertRaises(TypeError, Revoked, 1)
+        self.assertRaises(TypeError, Revoked, "foo")
+
+
+    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(ValueError, revoked.set_serial, 'pqrst')
+        self.assertRaises(TypeError, revoked.set_serial, 100)
+        self.assertRaises(TypeError, revoked.get_serial, 1)
+        self.assertRaises(TypeError, revoked.get_serial, None)
+        self.assertRaises(TypeError, revoked.get_serial, "")
+
+
+    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 )
+
+
+    def test_reason(self):
+        """
+        Confirm we can set and get revocation reasons from
+        L{OpenSSL.crypto.Revoked}.  The "get" need to work
+        as "set".  Likewise, each reason of all_reasons() must work.
+        """
+        revoked = Revoked()
+        for r in revoked.all_reasons():
+            for x in xrange(2):
+                ret = revoked.set_reason(r)
+                self.assertEqual( ret, None )
+                reason = revoked.get_reason()
+                self.assertEqual( reason.lower().replace(' ',''),
+                                       r.lower().replace(' ','') )
+                r = reason # again with the resp of get
+
+        revoked.set_reason(None)
+        self.assertEqual(revoked.get_reason(), None)
+
+
+    def test_set_reason_wrong_arguments(self):
+        """
+        Calling L{OpenSSL.crypto.Revoked.set_reason} with other than
+        one argument, or an argument which isn't a valid reason,
+        results in L{TypeError} or L{ValueError} being raised.
+        """
+        revoked = Revoked()
+        self.assertRaises(TypeError, revoked.set_reason, 100)
+        self.assertRaises(ValueError, revoked.set_reason, 'blue')
+
+
+    def test_get_reason_wrong_arguments(self):
+        """
+        Calling L{OpenSSL.crypto.Revoked.get_reason} with any
+        arguments results in L{TypeError} being raised.
+        """
+        revoked = Revoked()
+        self.assertRaises(TypeError, revoked.get_reason, None)
+        self.assertRaises(TypeError, revoked.get_reason, 1)
+        self.assertRaises(TypeError, revoked.get_reason, "foo")
+
+
+
+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_construction_wrong_args(self):
+        """
+        Calling L{OpenSSL.crypto.CRL} with any number of arguments
+        results in a L{TypeError} being raised.
+        """
+        self.assertRaises(TypeError, CRL, 1)
+        self.assertRaises(TypeError, CRL, "")
+        self.assertRaises(TypeError, CRL, 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')
+        revoked.set_reason('sUpErSeDEd')
+        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('Superseded')
+        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('Superseded')
+        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_add_revoked_keyword(self):
+        """
+        L{OpenSSL.CRL.add_revoked} accepts its single argument as the
+        I{revoked} keyword argument.
+        """
+        crl = CRL()
+        revoked = Revoked()
+        crl.add_revoked(revoked=revoked)
+        self.assertTrue(isinstance(crl.get_revoked()[0], Revoked))
+
+
+    def test_export_wrong_args(self):
+        """
+        Calling L{OpenSSL.CRL.export} with fewer than two or more than
+        four arguments, or with arguments other than the certificate,
+        private key, integer file type, and integer number of days it
+        expects, results in a L{TypeError} being raised.
+        """
+        crl = CRL()
+        self.assertRaises(TypeError, crl.export)
+        self.assertRaises(TypeError, crl.export, self.cert)
+        self.assertRaises(TypeError, crl.export, self.cert, self.pkey, FILETYPE_PEM, 10, "foo")
+
+        self.assertRaises(TypeError, crl.export, None, self.pkey, FILETYPE_PEM, 10)
+        self.assertRaises(TypeError, crl.export, self.cert, None, FILETYPE_PEM, 10)
+        self.assertRaises(TypeError, crl.export, self.cert, self.pkey, None, 10)
+        self.assertRaises(TypeError, crl.export, self.cert, FILETYPE_PEM, None)
+
+
+    def test_export_unknown_filetype(self):
+        """
+        Calling L{OpenSSL.CRL.export} with a file type other than
+        L{FILETYPE_PEM}, L{FILETYPE_ASN1}, or L{FILETYPE_TEXT} results
+        in a L{ValueError} being raised.
+        """
+        crl = CRL()
+        self.assertRaises(ValueError, crl.export, self.cert, self.pkey, 100, 10)
+
+
+    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')
+        revoked.set_reason('sUpErSeDEd')
+        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_get_revoked_wrong_args(self):
+        """
+        Calling L{OpenSSL.CRL.get_revoked} with any arguments results
+        in a L{TypeError} being raised.
+        """
+        crl = CRL()
+        self.assertRaises(TypeError, crl.get_revoked, None)
+        self.assertRaises(TypeError, crl.get_revoked, 1)
+        self.assertRaises(TypeError, crl.get_revoked, "")
+        self.assertRaises(TypeError, crl.get_revoked, "", 1, None)
+
+
+    def test_add_revoked_wrong_args(self):
+        """
+        Calling L{OpenSSL.CRL.add_revoked} with other than one
+        argument results in a L{TypeError} being raised.
+        """
+        crl = CRL()
+        self.assertRaises(TypeError, crl.add_revoked)
+        self.assertRaises(TypeError, crl.add_revoked, 1, 2)
+        self.assertRaises(TypeError, crl.add_revoked, "foo", "bar")
+
+
+    def test_load_crl(self):
+        """
+        Load a known CRL and inspect its revocations.  Both
+        PEM and DER formats are loaded.
+        """
+        crl = load_crl(FILETYPE_PEM, crlData)
+        revs = crl.get_revoked()
+        self.assertEqual(len(revs), 2)
+        self.assertEqual(revs[0].get_serial(), '03AB')
+        self.assertEqual(revs[0].get_reason(), None)
+        self.assertEqual(revs[1].get_serial(), '0100')
+        self.assertEqual(revs[1].get_reason(), 'Superseded')
+
+        der = _runopenssl(crlData, "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[0].get_reason(), None)
+        self.assertEqual(revs[1].get_serial(), '0100')
+        self.assertEqual(revs[1].get_reason(), 'Superseded')
+
+
+    def test_load_crl_wrong_args(self):
+        """
+        Calling L{OpenSSL.crypto.load_crl} with other than two
+        arguments results in a L{TypeError} being raised.
+        """
+        self.assertRaises(TypeError, load_crl)
+        self.assertRaises(TypeError, load_crl, FILETYPE_PEM)
+        self.assertRaises(TypeError, load_crl, FILETYPE_PEM, crlData, None)
+
+
+    def test_load_crl_bad_filetype(self):
+        """
+        Calling L{OpenSSL.crypto.load_crl} with an unknown file type
+        raises a L{ValueError}.
+        """
+        self.assertRaises(ValueError, load_crl, 100, crlData)
+
+
+    def test_load_crl_bad_data(self):
+        """
+        Calling L{OpenSSL.crypto.load_crl} with file data which can't
+        be loaded raises a L{OpenSSL.crypto.Error}.
+        """
+        self.assertRaises(Error, load_crl, FILETYPE_PEM, "hello, world")
+
 
 class SignVerifyTests(TestCase):
     """