reason codes for CRL
diff --git a/doc/pyOpenSSL.tex b/doc/pyOpenSSL.tex
index d43ad96..c7d3a6c 100644
--- a/doc/pyOpenSSL.tex
+++ b/doc/pyOpenSSL.tex
@@ -614,6 +614,15 @@
 
 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.
@@ -623,6 +632,12 @@
 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.
diff --git a/src/crypto/revoked.c b/src/crypto/revoked.c
index 8289408..99d00e1 100644
--- a/src/crypto/revoked.c
+++ b/src/crypto/revoked.c
@@ -3,6 +3,203 @@
 #include "crypto.h"
 
 
+/* 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\
@@ -16,9 +213,9 @@
 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);
+        /* returns a borrowed reference.  */
+        return _get_asn1_time(
+                ":get_rev_date", self->revoked->revocationDate, args);
 }
 
 static char crypto_Revoked_set_rev_date_doc[] = "\n\
@@ -36,17 +233,19 @@
 static PyObject*
 crypto_Revoked_set_rev_date(crypto_RevokedObj *self, PyObject *args)
 {
-	return _set_asn1_time(
-		"s:set_rev_date", self->revoked->revocationDate, 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 *buf = NULL;
-    int ret, pending;
+    PyObject *str = NULL;
+    int str_len;
+    char *tmp_str;
 
     /* Create a openssl BIO buffer */
     bio = BIO_new(BIO_s_mem());
@@ -54,32 +253,23 @@
         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) {
+    if(i2a_ASN1_INTEGER(bio, asn1_int) < 0)
         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;
-    }
+    /* 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);
-    bio = NULL;
-    return buf;
+    return str;
 
  err:
     if(bio) {
         BIO_free(bio);
     }
-    if(buf) {
-        Py_DECREF(buf);
+    if(str) {
+        Py_DECREF(str);
     }
     return NULL;
 }
@@ -125,7 +315,7 @@
         return NULL;
 
     if( ! BN_hex2bn(&serial, hex_str) ) {
-        PyErr_SetString(PyExc_TypeError, "bad hex string");
+        PyErr_SetString(PyExc_ValueError, "bad hex string");
         return NULL;
     }
 
@@ -147,7 +337,7 @@
 
     self = PyObject_New(crypto_RevokedObj, &crypto_Revoked_Type);
     if (self==NULL)
-	    return NULL;
+            return NULL;
     self->revoked = revoked;
     return self;
 }
@@ -163,6 +353,9 @@
     { #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),
diff --git a/test/test_crypto.py b/test/test_crypto.py
index 630ce01..641bdaa 100644
--- a/test/test_crypto.py
+++ b/test/test_crypto.py
@@ -1168,6 +1168,7 @@
         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_serial(self):
@@ -1186,7 +1187,7 @@
         ser = revoked.get_serial()
         self.assertEqual( ser, '31' )
 
-        self.assertRaises(TypeError, revoked.set_serial, 'pqrst')
+        self.assertRaises(ValueError, revoked.set_serial, 'pqrst')
         self.assertRaises(TypeError, revoked.set_serial, 100)
 
 
@@ -1207,6 +1208,34 @@
         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_bad_reasons(self):
+        """
+        Use L{OpenSSL.crypto.Revoked.set_reason} in bad ways.
+        """
+        revoked = Revoked()
+        self.assertRaises(TypeError, revoked.set_reason, 100)
+        self.assertRaises(ValueError, revoked.set_reason, 'blue')
+
 
 class CRLTests(TestCase):
     """
@@ -1236,18 +1265,21 @@
         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
@@ -1255,6 +1287,7 @@
         self.assertEqual(text, dumped_text)
 
 
+
     def test_get_revoked(self):
         """
         Use python to create a simple CRL with two revocations.  
@@ -1269,6 +1302,7 @@
         revoked.set_serial('3ab')
         crl.add_revoked(revoked)
         revoked.set_serial('100')
+        revoked.set_reason('sUpErSeDEd')
         crl.add_revoked(revoked)
 
         revs = crl.get_revoked()
@@ -1289,29 +1323,32 @@
 
         crl_txt = """
 -----BEGIN X509 CRL-----
-MIIBTTCBtzANBgkqhkiG9w0BAQQFADBYMQswCQYDVQQGEwJVUzELMAkGA1UECBMC
+MIIBWzCBxTANBgkqhkiG9w0BAQQFADBYMQswCQYDVQQGEwJVUzELMAkGA1UECBMC
 SUwxEDAOBgNVBAcTB0NoaWNhZ28xEDAOBgNVBAoTB1Rlc3RpbmcxGDAWBgNVBAMT
-D1Rlc3RpbmcgUm9vdCBDQRcNMDkwNzI1MDIxMjE0WhcNMDkxMTAyMDIxMjE0WjAu
-MBUCAgOrGA8yMDA5MDcyNDIxMTIxNFowFQICAQAYDzIwMDkwNzI0MjExMjE0WjAN
-BgkqhkiG9w0BAQQFAAOBgQApflU91pdbbSXNMLxRHAwz+2M2vzhmpFDYsX8gPe76
-GgrEY475v1CGJTdmKQnwosUx1tJ6HgoueAfTvzLGgVhqfeeR6BTjhnJH69rW+L6A
-w47xSB7rmUglsn3HlAdZl4tIex+SlH7AB1mEsWNJ0VA0mDEF01eOaBwBfEmK3zGd
-ng==
+D1Rlc3RpbmcgUm9vdCBDQRcNMDkwNzI2MDQzNDU2WhcNMTIwOTI3MDI0MTUyWjA8
+MBUCAgOrGA8yMDA5MDcyNTIzMzQ1NlowIwICAQAYDzIwMDkwNzI1MjMzNDU2WjAM
+MAoGA1UdFQQDCgEEMA0GCSqGSIb3DQEBBAUAA4GBAEBt7xTs2htdD3d4ErrcGAw1
+4dKcVnIWTutoI7xxen26Wwvh8VCsT7i/UeP+rBl9rC/kfjWjzQk3/zleaarGTpBT
+0yp4HXRFFoRhhSE/hP+eteaPXRgrsNRLHe9ZDd69wmh7J1wMDb0m81RG7kqcbsid
+vrzEeLDRiiPl92dyyWmu
 -----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[0].get_reason(), None)
         self.assertEqual(revs[1].get_serial(), '0100')
+        self.assertEqual(revs[1].get_reason(), 'Superseded')
 
         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[0].get_reason(), None)
         self.assertEqual(revs[1].get_serial(), '0100')
-
+        self.assertEqual(revs[1].get_reason(), 'Superseded')