Support Python long integers in X509.set_serial_number and X509.get_serial_number

Thanks to Gael Le Mignot (kilobug) for the patch to fix get_serial_number.
diff --git a/src/crypto/x509.c b/src/crypto/x509.c
index 13a4c71..2149d28 100644
--- a/src/crypto/x509.c
+++ b/src/crypto/x509.c
@@ -71,12 +71,20 @@
 crypto_X509_get_serial_number(crypto_X509Obj *self, PyObject *args)
 {
     ASN1_INTEGER *asn1_i;
+    BIGNUM *bignum;
+    char *hex;
+    PyObject *res;
 
     if (!PyArg_ParseTuple(args, ":get_serial_number"))
         return NULL;
 
     asn1_i = X509_get_serialNumber(self->x509);
-    return PyInt_FromLong(ASN1_INTEGER_get(asn1_i));
+    bignum = ASN1_INTEGER_to_BN(asn1_i, NULL);
+    hex = BN_bn2hex(bignum);
+    res = PyLong_FromString(hex, NULL, 16);
+    BN_free(bignum);
+    free(hex);
+    return res;
 }
 
 static char crypto_X509_set_serial_number_doc[] = "\n\
@@ -91,15 +99,91 @@
 static PyObject *
 crypto_X509_set_serial_number(crypto_X509Obj *self, PyObject *args)
 {
-    long serial;
+    long small_serial;
+    PyObject *serial = NULL;
+    PyObject *hex = NULL;
+    PyObject *format = NULL;
+    PyObject *format_args = NULL;
+    ASN1_INTEGER *asn1_i = NULL;
+    BIGNUM *bignum = NULL;
 
-    if (!PyArg_ParseTuple(args, "l:set_serial_number", &serial))
+    if (!PyArg_ParseTuple(args, "O:set_serial_number", &serial)) {
         return NULL;
+    }
 
-    ASN1_INTEGER_set(X509_get_serialNumber(self->x509), serial);
+    if (!PyInt_Check(serial) && !PyLong_Check(serial)) {
+        PyErr_SetString(
+            PyExc_TypeError, "serial number must be integer");
+        goto err;
+    }
+
+    if ((format_args = Py_BuildValue("(O)", serial)) == NULL) {
+        goto err;
+    }
+
+    if ((format = PyString_FromString("%x")) == NULL) {
+        goto err;
+    }
+
+    if ((hex = PyString_Format(format, format_args)) == NULL) {
+        goto err;
+    }
+
+    /**
+     * BN_hex2bn stores the result in &bignum.  Unless it doesn't feel like
+     * it.  If bignum is still NULL after this call, then the return value
+     * is actually the result.  I hope.  -exarkun
+     */
+    small_serial = BN_hex2bn(&bignum, PyString_AsString(hex));
+
+    Py_DECREF(format_args);
+    format_args = NULL;
+    Py_DECREF(format);
+    format = NULL;
+    Py_DECREF(hex);
+    hex = NULL;
+
+    if (bignum == NULL) {
+        if (ASN1_INTEGER_set(X509_get_serialNumber(self->x509), small_serial)) {
+            exception_from_error_queue();
+            goto err;
+        }
+    } else {
+        asn1_i = BN_to_ASN1_INTEGER(bignum, NULL);
+        BN_free(bignum);
+        bignum = NULL;
+        if (asn1_i == NULL) {
+            exception_from_error_queue();
+            goto err;
+        }
+        if (!X509_set_serialNumber(self->x509, asn1_i)) {
+            exception_from_error_queue();
+            goto err;
+        }
+        ASN1_INTEGER_free(asn1_i);
+        asn1_i = NULL;
+    }
 
     Py_INCREF(Py_None);
     return Py_None;
+
+  err:
+    if (format_args) {
+        Py_DECREF(format_args);
+    }
+    if (format) {
+        Py_DECREF(format);
+    }
+    if (hex) {
+        Py_DECREF(hex);
+    }
+    if (bignum) {
+        BN_free(bignum);
+    }
+    if (asn1_i) {
+        ASN1_INTEGER_free(asn1_i);
+    }
+    return NULL;
 }
 
 static char crypto_X509_get_issuer_doc[] = "\n\
diff --git a/test/test_crypto.py b/test/test_crypto.py
index b88ada2..dfc80f2 100644
--- a/test/test_crypto.py
+++ b/test/test_crypto.py
@@ -5,7 +5,7 @@
 from unittest import TestCase
 
 from OpenSSL.crypto import TYPE_RSA, TYPE_DSA, Error, PKey, PKeyType
-from OpenSSL.crypto import X509, X509Name, X509NameType
+from OpenSSL.crypto import X509, X509Type, X509Name, X509NameType
 from OpenSSL.crypto import X509Req, X509ReqType
 
 class _Python23TestCaseHelper:
@@ -293,3 +293,39 @@
         del request
         subject.commonName = "bar"
         self.assertEqual(subject.commonName, "bar")
+
+
+
+class X509Tests(TestCase, _Python23TestCaseHelper):
+    """
+    Tests for L{OpenSSL.crypto.X509}.
+    """
+    def test_construction(self):
+        """
+        L{X509} takes no arguments and returns an instance of L{X509Type}.
+        """
+        certificate = X509()
+        self.assertTrue(
+            isinstance(certificate, X509Type),
+            "%r is of type %r, should be %r" % (certificate,
+                                                type(certificate),
+                                                X509Type))
+
+
+    def test_serial_number(self):
+        """
+        The serial number of an L{X509Type} can be retrieved and modified with
+        L{X509Type.get_serial_number} and L{X509Type.set_serial_number}.
+        """
+        certificate = X509()
+        self.assertRaises(TypeError, certificate.set_serial_number)
+        self.assertRaises(TypeError, certificate.set_serial_number, 1, 2)
+        self.assertRaises(TypeError, certificate.set_serial_number, "1")
+        self.assertRaises(TypeError, certificate.set_serial_number, 5.5)
+        self.assertEqual(certificate.get_serial_number(), 0)
+        certificate.set_serial_number(1)
+        self.assertEqual(certificate.get_serial_number(), 1)
+        certificate.set_serial_number(2 ** 32 + 1)
+        self.assertEqual(certificate.get_serial_number(), 2 ** 32 + 1)
+        certificate.set_serial_number(2 ** 64 + 1)
+        self.assertEqual(certificate.get_serial_number(), 2 ** 64 + 1)