/*
 * pkcs12.c
 *
 * Copyright (C) AB Strakt 2001, All rights reserved
 *
 * Certificate transport (PKCS12) handling code, 
 * mostly thin wrappers around OpenSSL.
 * See the file RATIONALE for a short explanation of why 
 * this module was written.
 *
 * Reviewed 2001-07-23
 */
#include <Python.h>
#define crypto_MODULE
#include "crypto.h"

/* 
 * PKCS12 is a standard exchange format for digital certificates.  
 * See e.g. the OpenSSL homepage http://www.openssl.org/ for more information
 */

static void crypto_PKCS12_dealloc(crypto_PKCS12Obj *self);

static char crypto_PKCS12_get_certificate_doc[] = "\n\
Return certificate portion of the PKCS12 structure\n\
\n\
Arguments: self - The PKCS12 object\n\
           args - The Python argument tuple, should be empty\n\
Returns:   X509 object containing the certificate\n\
";
static PyObject *
crypto_PKCS12_get_certificate(crypto_PKCS12Obj *self, PyObject *args)
{
    if (!PyArg_ParseTuple(args, ":get_certificate"))
        return NULL;

    Py_INCREF(self->cert);
    return self->cert;
}

static char crypto_PKCS12_get_privatekey_doc[] = "\n\
Return private key portion of the PKCS12 structure\n\
\n\
Arguments: self - The PKCS12 object\n\
           args - The Python argument tuple, should be empty\n\
Returns:   PKey object containing the private key\n\
";
static PyObject *
crypto_PKCS12_get_privatekey(crypto_PKCS12Obj *self, PyObject *args)
{
    if (!PyArg_ParseTuple(args, ":get_privatekey"))
        return NULL;

    Py_INCREF(self->key);
    return self->key;
}

static char crypto_PKCS12_get_ca_certificates_doc[] = "\n\
Return CA certificates within of the PKCS12 object\n\
\n\
Arguments: self - The PKCS12 object\n\
           args - The Python argument tuple, should be empty\n\
Returns:   A newly created tuple containing the CA certificates in the chain,\n\
           if any are present, or None if no CA certificates are present.\n\
";
static PyObject *
crypto_PKCS12_get_ca_certificates(crypto_PKCS12Obj *self, PyObject *args)
{
    if (!PyArg_ParseTuple(args, ":get_ca_certificates"))
        return NULL;

    Py_INCREF(self->cacerts);
    return self->cacerts;
}

/*
 * ADD_METHOD(name) expands to a correct PyMethodDef declaration
 *   {  'name', (PyCFunction)crypto_PKCS12_name, METH_VARARGS, crypto_PKCS12_name_doc }
 * for convenience
 */
#define ADD_METHOD(name)        \
    { #name, (PyCFunction)crypto_PKCS12_##name, METH_VARARGS, crypto_PKCS12_##name##_doc }
static PyMethodDef crypto_PKCS12_methods[] =
{
    ADD_METHOD(get_certificate),
    ADD_METHOD(get_privatekey),
    ADD_METHOD(get_ca_certificates),
    { NULL, NULL }
};
#undef ADD_METHOD

/*
 * Constructor for PKCS12 objects, never called by Python code directly.
 * The strategy for this object is to create all the Python objects
 * corresponding to the cert/key/CA certs right away
 *
 * Arguments: p12        - A "real" PKCS12 object
 *            passphrase - Passphrase to use when decrypting the PKCS12 object
 * Returns:   The newly created PKCS12 object
 */
crypto_PKCS12Obj *
crypto_PKCS12_New(PKCS12 *p12, char *passphrase)
{
    crypto_PKCS12Obj *self;
    PyObject *cacertobj = NULL;

    X509 *cert = NULL;
    EVP_PKEY *pkey = NULL;
    STACK_OF(X509) *cacerts = NULL;

    int i, cacert_count = 0;

    /* allocate space for the CA cert stack */
    cacerts = sk_X509_new_null();

    /* parse the PKCS12 lump */
    if (!(cacerts && PKCS12_parse(p12, passphrase, &pkey, &cert, &cacerts)))
    {
        exception_from_error_queue();
        return NULL;
    }

    if (!(self = PyObject_GC_New(crypto_PKCS12Obj, &crypto_PKCS12_Type)))
        return NULL;

    self->cert = NULL;
    self->key = NULL;
    Py_INCREF(Py_None);
    self->cacerts = Py_None;

    if ((self->cert = (PyObject *)crypto_X509_New(cert, 1)) == NULL)
        goto error;

    if ((self->key = (PyObject *)crypto_PKey_New(pkey, 1)) == NULL)
        goto error;

    /* Make a tuple for the CA certs */
    cacert_count = sk_X509_num(cacerts);
    if (cacert_count > 0)
    {
        Py_DECREF(self->cacerts);
        if ((self->cacerts = PyTuple_New(cacert_count)) == NULL)
            goto error;

        for (i = 0; i < cacert_count; i++)
        {
            cert = sk_X509_value(cacerts, i);
            if ((cacertobj = (PyObject *)crypto_X509_New(cert, 1)) == NULL)
                goto error;
            PyTuple_SET_ITEM(self->cacerts, i, cacertobj);
        }
    }

    sk_X509_free(cacerts); /* don't free the certs, just the stack */
    PyObject_GC_Track(self);

    return self;
error:
    crypto_PKCS12_dealloc(self);
    return NULL;
}

/*
 * Find attribute
 *
 * Arguments: self - The PKCS12 object
 *            name - The attribute name
 * Returns:   A Python object for the attribute, or NULL if something went
 *            wrong
 */
static PyObject *
crypto_PKCS12_getattr(crypto_PKCS12Obj *self, char *name)
{
    return Py_FindMethod(crypto_PKCS12_methods, (PyObject *)self, name);
}

/*
 * Call the visitproc on all contained objects.
 *
 * Arguments: self - The PKCS12 object
 *            visit - Function to call
 *            arg - Extra argument to visit
 * Returns:   0 if all goes well, otherwise the return code from the first
 *            call that gave non-zero result.
 */
static int
crypto_PKCS12_traverse(crypto_PKCS12Obj *self, visitproc visit, void *arg)
{
    int ret = 0;

    if (ret == 0 && self->cert != NULL)
        ret = visit(self->cert, arg);
    if (ret == 0 && self->key != NULL)
        ret = visit(self->key, arg);
    if (ret == 0 && self->cacerts != NULL)
        ret = visit(self->cacerts, arg);
    return ret;
}

/*
 * Decref all contained objects and zero the pointers.
 *
 * Arguments: self - The PKCS12 object
 * Returns:   Always 0.
 */
static int
crypto_PKCS12_clear(crypto_PKCS12Obj *self)
{
    Py_XDECREF(self->cert);
    self->cert = NULL;
    Py_XDECREF(self->key);
    self->key = NULL;
    Py_XDECREF(self->cacerts);
    self->cacerts = NULL;
    return 0;
}

/*
 * Deallocate the memory used by the PKCS12 object
 *
 * Arguments: self - The PKCS12 object
 * Returns:   None
 */
static void
crypto_PKCS12_dealloc(crypto_PKCS12Obj *self)
{
    PyObject_GC_UnTrack(self);
    crypto_PKCS12_clear(self);
    PyObject_GC_Del(self);
}

PyTypeObject crypto_PKCS12_Type = {
    PyObject_HEAD_INIT(NULL)
    0,
    "PKCS12",
    sizeof(crypto_PKCS12Obj),
    0,
    (destructor)crypto_PKCS12_dealloc,
    NULL, /* print */
    (getattrfunc)crypto_PKCS12_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 | Py_TPFLAGS_HAVE_GC,
    NULL, /* doc */
    (traverseproc)crypto_PKCS12_traverse,
    (inquiry)crypto_PKCS12_clear,
};

/*
 * Initialize the PKCS12 part of the crypto sub module
 *
 * Arguments: dict - The crypto module dictionary
 * Returns:   None
 */
int
init_crypto_pkcs12(PyObject *dict)
{
    crypto_PKCS12_Type.ob_type = &PyType_Type;
    Py_INCREF(&crypto_PKCS12_Type);
    PyDict_SetItemString(dict, "PKCS12Type", (PyObject *)&crypto_PKCS12_Type);
    return 1;
}

