#include <Python.h>

#include <openssl/asn1.h>
#include <openssl/rsa.h>
#include <openssl/err.h>
#include <openssl/ssl.h>
#include <openssl/pem.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>

#define SERIAL_RAND_BITS  128

#ifdef PYTHON24
#define Py_ssize_t   int
#endif

static void delcert(void *ptr)
{
    X509_free((X509 *)ptr);
}

/* We may want to make this more generic and able to cope with any
 * possible entry in an X509_NAME structure?
 */
static PyObject *
parseX509Name(X509_NAME *name)
{
    PyObject *sdict = PyDict_New();
    int i;
    struct sParts {
        int nid;
        const char *dict;
    } parts[] = {
        { NID_commonName, "CN" },
        { NID_countryName, "C" },
        { NID_organizationName, "O" },
        { NID_organizationalUnitName, "OU" },
        { NID_localityName, "L" },
        { NID_stateOrProvinceName, "ST" },
        { NID_pkcs9_emailAddress, "Email" },
        { 0, NULL }
    };

    for (i = 0; parts[i].nid != 0; i++) {
        char buf[512];
        int rv = X509_NAME_get_text_by_NID(name, parts[i].nid, buf, 512);
        if (rv > 0)
            PyDict_SetItemString(sdict, parts[i].dict,
                           PyString_FromStringAndSize(buf, rv));
    }
    return sdict;
}

static PyObject *
makePyObjectFromASN1Time(ASN1_TIME *tm)
{
    BIO *bio = BIO_new(BIO_s_mem());
    PyObject *rv = NULL;
    char buf[2048];
    int len;

    if (bio) {
        ASN1_TIME_print(bio, tm);
        len = BIO_gets(bio, buf, sizeof(buf)-1);
        rv = PyString_FromStringAndSize(buf, len);
        BIO_free_all(bio);
    }
    return rv;
}

static PyObject *
makePyStringFromASN1Integer(ASN1_INTEGER *i)
{
    BIO *bio = BIO_new(BIO_s_mem());
    PyObject *rv = NULL;
    char buf[2048];
    int len;

    if (bio) {
        i2a_ASN1_INTEGER(bio, i);
        len = BIO_gets(bio, buf, sizeof(buf)-1);
        rv = PyString_FromStringAndSize(buf, len);
        BIO_free_all(bio);
    }
    return rv;
}

static PyObject *
makePyObjectFromPrintf(const char *fmt, ...)
{
    PyObject *rv = NULL;
    char msg[2048];
    int slen = 0;
    va_list args;

    memset(msg, 0, sizeof(msg));
    va_start(args, fmt);
    slen = vsnprintf(msg, sizeof(msg), fmt, args);
    va_end(args);

    if (slen > 0)
        rv = PyString_FromStringAndSize(msg, slen);

    return rv;
}

static void
addX509PurposeToPyDict(X509 *cert, X509_PURPOSE *p, PyObject *dict, 
                       PyObject *cadict)
{
    int i;
    int id = X509_PURPOSE_get_id(p);
    char *pname = X509_PURPOSE_get0_name(p);
    PyObject *key = makePyObjectFromPrintf("%s", pname);

    for (i = 0; i < 2; i++) {
        int idret = X509_check_purpose(cert, id, i);
        PyObject *val = NULL;

        if (idret == 1)
            val = makePyObjectFromPrintf("%s", "Yes");
        else if (idret == 0)
            val = makePyObjectFromPrintf("%s", "No");
        else
            val = makePyObjectFromPrintf("Yes (Warning code %d)", idret);

        if (!val)
            continue;

        if (i == 0)
           PyDict_SetItem(dict, key, val);
        else
           PyDict_SetItem(cadict, key, val);

        Py_DECREF(val);
    }
    Py_DECREF(key);
}

/* Presently this just uses a random number, but a more appealling solution
 * is to switch to using a hash of certain key elements. Apparently Verisign do
 * something similar and it seems like a damned good idea. The suggested
 * fields to hash are
 * - subject
 * - notBefore
 * - not After
 * The reason for this function is to allow easier abstraction :-)
 */
static int 
certificate_set_serial(X509 *cert)
{
    ASN1_INTEGER *sno = ASN1_INTEGER_new();
    BIGNUM *bn = NULL;
    int rv = 0;

    if (!sno) {
        PyErr_SetString(PyExc_MemoryError, "Unable to allocate memory for "
                                           "an ASN1 object");
        return 0;
    }
    bn=BN_new();
    if (!bn) {
        ASN1_INTEGER_free(sno);
        PyErr_SetString(PyExc_MemoryError, "Unable to allocate memory "
                                           "for an BIGNUM object");
        return 0;
    }

    if (BN_pseudo_rand(bn, SERIAL_RAND_BITS, 0, 0) == 1 &&
        (sno = BN_to_ASN1_INTEGER(bn,sno)) != NULL &&
        X509_set_serialNumber(cert, sno) == 1)
        rv = 1;
    else
        PyErr_SetString(PyExc_RuntimeError, "Unable to create or set "
                                            "the serial number");
    BN_free(bn);
    ASN1_INTEGER_free(sno);
    return rv;
}

static int 
certificateAddExtension(X509 *cert, X509V3_CTX *ctx, char *key, char *val)
{
    X509_EXTENSION * ext = NULL;
    int rv;

    if (!(ext = X509V3_EXT_conf(NULL, ctx, key, val))) {
        PyObject *errStr = makePyObjectFromPrintf("Unable to create X509 "
                   "v3 extension (key = %s, val = %s)", key, val);
        PyErr_SetObject(PyExc_RuntimeError, errStr);
        return 0;
    }
    rv = X509_add_ext(cert, ext, -1);
    X509_EXTENSION_free(ext);
    if (rv != 1) {
        PyObject *errStr = makePyObjectFromPrintf("Unable to set X509 "
                   "v3 extension (key = %s, val = %s)", key, val);
        PyErr_SetObject(PyExc_RuntimeError, errStr);
        return 0;
    }
    return 1;
}

static int 
removeNameFields(X509_NAME *subj, PyObject *list)
{
    Py_ssize_t sz = PyList_Size(list), pos = 0;

    for (pos = 0; pos < sz; pos++) {
        PyObject *pVal = PyList_GetItem(list, pos);
        char *val = PyString_AsString(pVal);
        int nid = 0, nPos = -1;

        if ((nid = OBJ_sn2nid(val)) == NID_undef &&
            (nid = OBJ_ln2nid(val)) == NID_undef) {
            PyObject *errStr = makePyObjectFromPrintf("Unable to find NID "
                         "for string '%s'", val);
            PyErr_SetObject(PyExc_RuntimeError, errStr);
            return 0;
        }
        while ((nPos = X509_NAME_get_index_by_NID(subj, nid, nPos)) != -1) {
            X509_NAME_ENTRY *ne = X509_NAME_delete_entry(subj, nPos);
            if (ne)
                X509_NAME_ENTRY_free(ne);
        }

    }
    return 1;
}

static int
mergeSubjects(X509_NAME *to, X509_NAME *from, PyObject *ignore)
{
    int i, sz = X509_NAME_entry_count(from);
    Py_ssize_t iSz = 0, iPos = 0;
    int *nids = NULL;

    if (ignore) {
        iSz = PyList_Size(ignore);
        nids = (int *)malloc(sizeof(int) * (int)iSz);
        if (!nids) {
            return 0;
        }
        for (i = 0, iPos = 0; iPos < iSz; iPos++) {
            PyObject *pVal = PyList_GetItem(ignore, iPos);
            char *val = PyString_AsString(pVal);
            int nid = 0;

            if ((nid = OBJ_sn2nid(val)) == NID_undef &&
                (nid = OBJ_ln2nid(val)) == NID_undef) {
                PyObject *errStr = makePyObjectFromPrintf("Unable to find NID "
                             "for string '%s'", val);
                PyErr_SetObject(PyExc_RuntimeError, errStr);
                return 0;
            }
            nids[i++] = nid;
        }
    }

    for (i = 0; i < sz; i++) {
        X509_NAME_ENTRY *ne = X509_NAME_get_entry(from, i);

        if (ignore) {
            int j;
            int skip = 0;
            for (j = 0; j < (int)iSz; j++) {
                int fPos = -1;

                do {
                    fPos = X509_NAME_get_index_by_NID(from, nids[j], fPos);
                    if (fPos == i)
                        skip = 1;
                } while (fPos != -1);
            }
            if (skip)
                continue;
        }
        if (!(X509_NAME_add_entry(to, ne, -1, 0))) {
            PyErr_SetString(PyExc_RuntimeError, "Unable to adjust the "
                                 "subject for the new certificate");
            return 0;
        }
    }
    return 1;
}

static X509_NAME *
makeSubjectFromDict(PyObject *dict, unsigned long chtype)
{
    X509_NAME *subj = X509_NAME_new();
    int nid;
    PyObject *pKey = NULL, *pValue = NULL;
    int pos = 0;
    
    while (PyDict_Next(dict, &pos, &pKey, &pValue)) {
        char *key = PyString_AsString(pKey);
        char *val = PyString_AsString(pValue);

        if ((nid = OBJ_txt2nid(key)) == NID_undef &&
            (nid = OBJ_sn2nid(key)) == NID_undef) {
            printf("Failed to find key %s\n", key);
            continue;
        }

        X509_NAME_add_entry_by_NID(subj, nid, chtype, 
                                   (unsigned char *)val,
                                   -1, -1, 0);
    }
    return subj;
}

static int
addV3Attributes(X509 *cert, X509 *issuer, PyObject *attrDict)
{
    X509V3_CTX ctx;
    int pos = 0;
    PyObject *pKey = NULL, *pValue = NULL;

    X509V3_set_ctx(&ctx, issuer ? issuer : cert, cert, NULL, NULL, 0);
    
    while (PyDict_Next(attrDict, &pos, &pKey, &pValue)) {
        char *key = PyString_AsString(pKey);
        char *val = PyString_AsString(pValue);
        if (certificateAddExtension(cert, &ctx, key, val) == 0)
            return 0;
    }
    return 1;
}

static int 
signCertificateWithKey(X509 *cert, RSA *rsa)
{
    int rv = 1;
    const EVP_MD *digest = EVP_sha1();
    EVP_PKEY *pkey = EVP_PKEY_new();

    if (!digest) {
        PyErr_SetString(PyExc_RuntimeError, "Unable to get a digest object");
        return 0;
    }

    if (!pkey) {
        PyErr_SetString(PyExc_MemoryError, "Unable to create a new EVP_PKEY object");
        return 0;
    }
    EVP_PKEY_assign_RSA(pkey, rsa); 

    /* need to verify here that the key we have was the one used to 
     * create the certificate, or we get some very "odd" results.
     * Not cool!
     */
//    if (X509_check_private_key(cert, pkey) == 1) {
        if (X509_sign(cert, pkey, digest))
            rv = 1;
        else
            PyErr_SetString(PyExc_RuntimeError, "Error signing the "
                                      "certificate using supplied key");
//    } else
//        PyErr_SetString(PyExc_RuntimeError, "Private key supplied is NOT "
//                       "the key used to create the certificate!");

    EVP_PKEY_free(pkey);
    return rv;
}

static X509* createCertificate(X509_NAME *subject, X509_NAME *issuer,
                               EVP_PKEY *pubKey, RSA *privKey, 
                               long startOffset, long period,
                               PyObject *attributes, X509 *caCert)

{
    X509 *cert = NULL;

//    if (caCert && X509_check_private_key(caCert, pkey) != 1) {
//        PyErr_SetString(PyExc_RuntimeError, "Private key supplied is NOT "
//                       "the key used to create the certificate!");
//        return NULL;
//    }

    cert = X509_new();
    if (!cert) {
        PyErr_SetString(PyExc_MemoryError, "Unable to get a new X509 "
                                           "certificate structure");
        return NULL;
    }
    /* We're creating a Version 3 certificate, so set that before we do
     * anything else.
     */
    if (X509_set_version(cert, 2L) != 1) {
        PyErr_SetString(PyExc_RuntimeError, "Unable to set the version "
                                            "of the new certificate");
        goto out;
    }
    /* Set the subject of the new certificate */
    if (X509_set_subject_name(cert, subject) != 1) {
        PyErr_SetString(PyExc_RuntimeError, "Unable to set the subject "
                                      "name of the new certificate");
        goto out;
    }
    /* Set the issuer subject line */
    if (X509_set_issuer_name(cert, issuer) != 1) {
        PyErr_SetString(PyExc_RuntimeError, "Unable to set the issuer "
                                     "name of the new certificate");
        goto out;
    }
    /* Add the public key we'll be using */
    if (X509_set_pubkey(cert, pubKey) != 1) {
        PyErr_SetString(PyExc_RuntimeError, "Unable to set the public "
                                      "key of the new certificate");
        goto out;
    }
    /* Set the initial date/times */
    if (!(X509_gmtime_adj(X509_get_notBefore(cert), startOffset))) {
        PyErr_SetString(PyExc_RuntimeError, "Unable to set notBefore time");
        goto out;
    }
    /* Set the validity period of the certificate by setting it's
     * expiration date.
     */
    if (!(X509_gmtime_adj(X509_get_notAfter(cert), startOffset + period))) {
        PyErr_SetString(PyExc_RuntimeError, "Unable to set notAfter time");
        goto out;
    }
    /* Set the serial number of the certificate. */
    if (certificate_set_serial(cert) == 0)
        goto out;
    /* Add the X509_v3 extensions we need to set. If this fails
     * the exeception will already have been set, so just bail out.
     */
    if (attributes && addV3Attributes(cert, caCert, attributes) != 1)
        goto out;

    /* Claim credit for the creation... 
     * We don't care if this fails ;-)
     */
    certificateAddExtension(cert, NULL, "nsComment", "Created by BaDCA");

    /* Finally, sign the certificate with the private key of the issuing
     * certificate. Once this is done, the certificate is complete.
     */
    if (signCertificateWithKey(cert, privKey) ==1)
        return cert;

out:
    X509_free(cert);
    return NULL;
}

/***********************************************************************
 **
 ** Functions below this header are referenced from outside this file,
 ** i.e. they appear in the list of methods exported from the module
 **
 **********************************************************************/
static PyObject *
readCert(PyObject *self, PyObject *args)
{
    X509 *cert = NULL;
    char *fn = NULL;
    BIO *in = NULL;
    PyObject *rv = NULL;

    if (!PyArg_ParseTuple(args, "z", &fn))
        return NULL;

    if (!fn) {
        PyErr_SetString(PyExc_ValueError, "Filename MUST be supplied");
        return NULL;
    }

    in = BIO_new(BIO_s_file());
    if (!in) {
        PyErr_SetString(PyExc_MemoryError, "failed to create a BIO");
        return NULL;
    }
    BIO_read_filename(in, fn);
    cert = PEM_read_bio_X509_AUX(in, NULL, NULL, NULL);
    (void) BIO_free_all(in);

    if (cert)
        return PyCObject_FromVoidPtr(cert, delcert);

    PyErr_SetString(PyExc_IOError, "Failed to read certificate file");
    return rv;
}

static PyObject *
writeCert(PyObject *self, PyObject *args)
{
    char *fn = NULL;
    X509 *cert = NULL;
    void *tmp = NULL;
    BIO *bio = NULL;
    int rv;

    if (! PyArg_ParseTuple(args, "Oz", &tmp, &fn))
        return NULL;
    cert = (X509 *)PyCObject_AsVoidPtr(tmp);
    if (!cert || !fn) {
        if (!cert)
            PyErr_SetString(PyExc_ValueError, "No certificate to write!");
        else
            PyErr_SetString(PyExc_ValueError, "No filename supplied!");
        return NULL;
    }

    bio = BIO_new(BIO_s_file());
    if (!bio) {
        PyErr_SetString(PyExc_MemoryError, "Unable to create a BIO");
        return NULL;
    }

    rv = BIO_write_filename(bio, fn);
    if (rv == 1) {
        rv = PEM_write_bio_X509(bio, cert);
        (void)BIO_flush(bio);
    }
    BIO_free_all(bio);

    return Py_BuildValue("i", rv);
}

static PyObject *
parseCertificate(PyObject *self, PyObject *args)
{
    void *tmp = NULL;
    PyObject *dict = NULL;
    X509 *cert = NULL;
    X509_NAME *subject = NULL, *issuer = NULL;
    const EVP_MD *digest = EVP_sha1();

    if (! PyArg_ParseTuple(args, "O", &tmp))
        return NULL;

    cert = (X509 *)PyCObject_AsVoidPtr(tmp);
    if (!cert) {
        PyErr_SetString(PyExc_ValueError, "Invalid certificate object supplied");
        return NULL;
    }
    subject = X509_get_subject_name(cert);
    issuer = X509_get_issuer_name(cert);
    if (! subject || ! issuer) {
        PyErr_SetString(PyExc_ValueError, "Unable to get issuer or subject details");
        goto err;
    }

    dict = PyDict_New();

    /* What follows isn't ideal, so if you have ideas how to improve it... */

    /* certificate version number */
    {
        int ver = X509_get_version(cert);
        if (ver > 0)
            PyDict_SetItemString(dict, "Version",
                             Py_BuildValue("i", (ver + 1)));

    }
    /* Serial number as a hex string */
    {
        PyObject *serStr = makePyStringFromASN1Integer(X509_get_serialNumber(cert));
        if (serStr) {
	    PyDict_SetItemString(dict, "serial", serStr);
            Py_DECREF(serStr);
        }
    }
    /* certificate fingerprint */
    {
        int j;
        unsigned int n;
        unsigned char md[EVP_MAX_MD_SIZE];

        if (X509_digest(cert, digest, md, &n)) {
            PyObject *fdict = PyDict_New(), *val = NULL;
            char *str = (char *)malloc(3 * n);

            val = makePyObjectFromPrintf("%s", OBJ_nid2sn(EVP_MD_type(digest)));
            PyDict_SetItemString(fdict, "digest", val);
            Py_DECREF(val);
            memset(str, 3 * n, 0);
            for (j=0; j < (int)n; j++) {
                sprintf(&str[3 * j], "%02X", md[j]);
                if (j + 1 < (int)n)
                    str[(3 * j) + 2] = ':';
            }
            val = makePyObjectFromPrintf("%s", str);
            PyDict_SetItemString(fdict, "value", val);
            Py_DECREF(val);
            PyDict_SetItemString(dict, "fingerprint", fdict);
            Py_DECREF(fdict);
            free(str);
        }
    }

    /* Public Key information */
    {
        EVP_PKEY *pkey = X509_get_pubkey(cert);
        PyObject *pkdict = PyDict_New();

        /* Public Key Algorithm */
        {
            X509_PUBKEY *pk = X509_get_X509_PUBKEY(cert);
            BIO *bp = BIO_new(BIO_s_mem());
            if (bp && i2a_ASN1_OBJECT(bp, pk->algor->algorithm) > 0) {
                char *strPtr = NULL;
                long sz = BIO_get_mem_data(bp, &strPtr);
                (void)BIO_set_close(bp, BIO_NOCLOSE);
                BIO_free(bp);
                PyDict_SetItemString(pkdict, "algorithm",
                               PyString_FromStringAndSize(strPtr, sz));
            }
        }

        /* Public key Strength */
        if (pkey->type == EVP_PKEY_RSA) 
            PyDict_SetItemString(pkdict, "strength",
                           Py_BuildValue("l", BN_num_bits(pkey->pkey.rsa->n)));

        PyDict_SetItemString(dict, "public_key", pkdict);
    }
    {
        PyObject *ndict = parseX509Name(subject);
        PyDict_SetItemString(dict, "subject", ndict);
        Py_DECREF(ndict);
    }
    {
        PyObject *ndict = parseX509Name(issuer);
        PyDict_SetItem(dict, Py_BuildValue("s", "issuer"), ndict);
        Py_DECREF(ndict);
    }
    {
        PyObject *obj = makePyObjectFromASN1Time(X509_get_notBefore(cert));
        if (obj) {
	    PyDict_SetItemString(dict, "notBefore", obj);
            Py_DECREF(obj);
        }
    }
    {
        PyObject *obj = makePyObjectFromASN1Time(X509_get_notAfter(cert));
        if (obj) {
	    PyDict_SetItemString(dict, "notAfter", obj);
            Py_DECREF(obj);

        }
    }

    /* get a list of purposes */
    {
        X509_PURPOSE *ptmp;
        int j;
        if (X509_PURPOSE_get_count() > 0) {
            PyObject *adict = PyDict_New();
            PyObject *pdict = PyDict_New(), *pcadict = PyDict_New();
            for (j = 0; j < X509_PURPOSE_get_count(); j++) {
                ptmp = X509_PURPOSE_get0(j);
                addX509PurposeToPyDict(cert, ptmp, pdict, pcadict);
            }
            PyDict_SetItemString(adict, "NonCA", pdict);
            PyDict_SetItemString(adict, "CA", pcadict);
            Py_DECREF(pdict);
            Py_DECREF(pcadict);
            PyDict_SetItemString(dict, "purpose", adict);
            Py_DECREF(adict);

        }
    }
    /* Get the list of attributes */
    {

    }


    return dict;
err:
    X509_free(cert);
    return NULL;
}

static PyObject *
signRequestWithCertificate(PyObject *self, PyObject *args)
{
    X509 *cert = NULL, *issuer = NULL;
    X509_REQ *req = NULL;
    X509_NAME *xn_req = NULL, *subject = NULL;
    X509_NAME *issuerSubject = NULL;
    RSA *key = NULL;
    EVP_PKEY *pkey = NULL;
    PyObject *optionsDict = NULL, *attrDict = NULL;
    PyObject *removeList = NULL, *ignoreList = NULL;
    void *tmp[3];

    if (! PyArg_ParseTuple(args, "OOO|O", &tmp[0], &tmp[1], &tmp[2], 
                           &optionsDict))
        return NULL;

    issuer = (X509 *)PyCObject_AsVoidPtr(tmp[0]);
    req = (X509_REQ *)PyCObject_AsVoidPtr(tmp[1]);
    key = (RSA *)PyCObject_AsVoidPtr(tmp[2]);

    if (!issuer || !req || !key) {
        PyErr_SetString(PyExc_ValueError, "Invalid parameters passed");
        return NULL;
    }

    if (optionsDict) {
        attrDict = PyDict_GetItemString(optionsDict, "attributes");
        removeList = PyDict_GetItemString(optionsDict, "remove");
        ignoreList = PyDict_GetItemString(optionsDict, "ignore");
    }

    issuerSubject = X509_get_subject_name(issuer);
    if (! issuerSubject) {
        PyErr_SetString(PyExc_RuntimeError, "Unable to get the "
                                    "subject name from the issuer");
        goto out;
    }

    subject = X509_NAME_dup(issuerSubject);       
    if (! subject) {
        PyErr_SetString(PyExc_RuntimeError, "Unable to duplicate the "
                                    "subject name from the issuer");
        goto out;
    }

    /* We create the subject for the new certificate by copying the
     * subject of the issuing certificate, which means we may feel the
     * need to remove some of the fields before we add the new ones.
     * Do this before we add any additional ones we need, but only if
     * we havea list of items to remove.
     */
    if (removeList && removeNameFields(subject, removeList) != 1)
        goto out;

    /* Get the subject from the request. */
    if (!(xn_req = X509_REQ_get_subject_name(req))) {
        PyErr_SetString(PyExc_RuntimeError, "Unable to get the "
                                    "subject name from the request");
        goto out;
    }
    /* Create the final subject for our new certificate. This is done
     * by merging the issuer subject (after adjustments) and the subject
     * from the request. Optionally, some fields from the request subject
     * can be ignored.
     */
    if (mergeSubjects(subject, xn_req, ignoreList) != 1)
        goto out;

    /* The public key associated with the generated certificate is the
     * one that was used to create the signing request. Extract the 
     * public key from the request and set it into the new certificate.
     */
    pkey = X509_REQ_get_pubkey(req);
    if (!pkey) {
        PyErr_SetString(PyExc_RuntimeError, "Unable to get the public key "
                "from the supplied request");
        goto out;
    }

    cert = createCertificate(subject, issuerSubject, pkey, key,
                             0, 36400, attrDict, issuer);

out:
    /* Free the structures we need to. */
    if (pkey)
        EVP_PKEY_free(pkey);
    if (subject)
        X509_NAME_free(subject);

    /* If we have a certificate, return it. Otherwise return NULL as the
     * error will have been set by the function that caused it :-)
     */
    if (cert)
        return PyCObject_FromVoidPtr(cert, delcert);
    return NULL;
}

static PyObject *
createSelfSignedCertificate(PyObject *self, PyObject *args)
{
    PyObject *subjectDict = NULL;
    PyObject *optionsDict = NULL, *attrDict = NULL;
    void *tmp = NULL;
    X509_NAME *subject = NULL;
    RSA *key = NULL;
    X509 *cert = NULL;
    EVP_PKEY *pkey = NULL;
    unsigned long chtype = MBSTRING_ASC;

    if (! PyArg_ParseTuple(args, "OO|O", &subjectDict, &tmp, 
                           &optionsDict))
        return NULL;

    key = (RSA *)PyCObject_AsVoidPtr(tmp);
    if (!key || !subjectDict)
        return NULL;

    if (optionsDict)
        attrDict = PyDict_GetItem(optionsDict, Py_BuildValue("s", "attributes"));

    pkey = EVP_PKEY_new();
    if (!pkey) {
        PyErr_SetString(PyExc_RuntimeError, "Unable to create PKEY object");
        return NULL;
    }
    EVP_PKEY_assign_RSA(pkey, key);

    subject = makeSubjectFromDict(subjectDict, chtype);
    if (!subject) {
        PyErr_SetString(PyExc_RuntimeError, "Unable to create subject");
        return NULL;
    }

    cert = createCertificate(subject, subject, pkey, key,
                             0, 36400, attrDict, NULL);

    X509_NAME_free(subject);

    if (cert)
        return PyCObject_FromVoidPtr(cert, delcert);
    return NULL;
}

static PyObject *
getCertificateSerial(PyObject *self, PyObject *args)
{
    X509 *cert = NULL;
    void *tmp = NULL;
    char *serial = NULL;

    if (! PyArg_ParseTuple(args, "O", &tmp))
        return NULL;

    cert = (X509 *)PyCObject_AsVoidPtr(tmp);
    if (!cert) {
        PyErr_SetString(PyExc_ValueError, "No certificate object supplied");
        return NULL;
    }
    serial = i2s_ASN1_INTEGER(NULL, X509_get_serialNumber(cert));
    return Py_BuildValue("s", serial);
}

static PyObject *
getCertificateModulus(PyObject *self, PyObject *args)
{
    X509 *cert = NULL;
    void *tmp = NULL;
    EVP_PKEY *pkey = NULL;
    PyObject *rv = NULL;

    if (! PyArg_ParseTuple(args, "O", &tmp))
        return NULL;

    cert = (X509 *)PyCObject_AsVoidPtr(tmp);
    if (!cert) {
        PyErr_SetString(PyExc_ValueError, "No certificate object supplied");
        return NULL;
    }

    pkey=X509_get_pubkey(cert);
    if (pkey && pkey->type == EVP_PKEY_RSA) {
        char *modulus = NULL;
        long sz = 0;
        BIO *out = BIO_new(BIO_s_mem());
        if (out) {
            BN_print(out, pkey->pkey.rsa->n);
            (void)BIO_flush(out);

            sz = BIO_get_mem_data(out, &modulus);
            if (sz && modulus)
                (void)BIO_set_close(out, BIO_NOCLOSE);
            BIO_free(out);
            if (modulus && sz > 0)
                rv = PyString_FromStringAndSize(modulus, sz);
            else
                PyErr_SetString(PyExc_RuntimeError,
                "Unable to allocate memory for the public key modulus");
        } else
            PyErr_SetString(PyExc_RuntimeError, "Unable to create a BIO");
    }
    EVP_PKEY_free(pkey);

    return rv;
}

static PyMethodDef CertMethods[] = {
    { "read", readCert, METH_VARARGS, "Read a certificate from a file" },
    { "write", writeCert, METH_VARARGS, "Write a certificate to a file" },
    { "parse", parseCertificate, METH_VARARGS, "Parse a certificate" },
    { "signRequest", signRequestWithCertificate, METH_VARARGS,
         "Create a certificate from a CSR using a root certificate" },
    { "createCertificateFromDict", createSelfSignedCertificate, METH_VARARGS,
         "Create a self signed 'root' certificate using a dict for the subject" },
    { "getSerial", getCertificateSerial, METH_VARARGS, 
             "Get the serial from a certificate" },
    { "getModulus", getCertificateModulus, METH_VARARGS,
             "Get the modulus of the certificate's public key" },
    { NULL, NULL, 0, NULL },
};

PyMODINIT_FUNC
initcert(void)
{
    PyObject *mod = NULL;

    CRYPTO_malloc_init();
    ERR_load_crypto_strings(); 
    OpenSSL_add_all_algorithms();

    mod = Py_InitModule("cert", CertMethods);
}

