/*
 * 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"

static char *CVSid = "@(#) $Id: pkcs12.c,v 1.2 2004/09/23 14:25:28 murata Exp $";

/* 
 * 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_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 */

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

/*
 * Deallocate the memory used by the PKCS12 object
 *
 * Arguments: self - The PKCS12 object
 * Returns:   None
 */
static void
crypto_PKCS12_dealloc(crypto_PKCS12Obj *self)
{
    Py_XDECREF(self->cert);
    Py_XDECREF(self->key);
    Py_XDECREF(self->cacerts);

    PyObject_Del(self);
}

/*
 * 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);
}

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 */
};

/*
 * 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;
}

