#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 MINBITS 1024
#define MAXBITS 8196

#define SERIAL_RAND_BITS  128

/* 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) {
        printf("Unable to allocate memory for "
                                           "an ASN1 object");
        return 0;
    }
    bn=BN_new();
    if (!bn) {
        ASN1_INTEGER_free(sno);
        printf("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
        printf("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))) {
        printf("Unable to create X509 "
                   "v3 extension (key = %s, val = %s)", key, val);
        return 0;
    }
    rv = X509_add_ext(cert, ext, -1);
    X509_EXTENSION_free(ext);
    if (rv != 1) {
        printf("Unable to set X509 "
                   "v3 extension (key = %s, val = %s)", key, val);
        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) {
        printf("Unable to get a digest object\n");
        return 0;
    }

    if (!pkey) {
        printf("Unable to create a new EVP_PKEY object\n");
        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
            printf("Error signing the "
                                      "certificate using supplied key");
    } else
        printf("Private key supplied is NOT "
                       "the key used to create the certificate!");

    EVP_PKEY_free(pkey);
    return rv;
}

RSA *readRSA(const char *fn)
{
    RSA *rsa = NULL;
    EVP_PKEY *pkey = NULL;
    BIO *in = BIO_new(BIO_s_file());

    if (!in) {
        printf("Unable to create a BIO object");
        return NULL;
    }

    if (BIO_read_filename(in, fn) == 1) {
        pkey=PEM_read_bio_PrivateKey(in, NULL, NULL, NULL);

        if (pkey) {
            printf("evp_pkey -> rsa = %p\n", pkey->pkey.rsa);
            rsa = EVP_PKEY_get1_RSA(pkey);
            RSA_up_ref(rsa);
            printf("rsa = %p\n", rsa);
            EVP_PKEY_free(pkey);
        } else
            printf("Unable to get public RSA key from file");
    } else
        printf("Unable to read from file supplied");

    (void)BIO_free_all(in);

    if (rsa) {
        int strength = BN_num_bits(rsa->n);
        if (strength < MINBITS || strength > MAXBITS) {
            RSA_free(rsa);
            rsa = NULL;
            printf("Invalid key strength");
        }
    }

    return rsa;
}

X509_REQ *readCSR(const char *fn)
{
    X509_REQ *req=NULL;
    BIO *in = NULL;

    if (! fn)
        return NULL;

    in = BIO_new(BIO_s_file());
    if (!in) {
        printf("Unable to create a BIO object");
        return NULL;
    }
    
    if (BIO_read_filename(in, fn) <= 0) {
        BIO_free_all(in);
        printf("Unable to read CSR from filename");
        return NULL;
    }

    /* We expect the CSR to be in PEM format, so try that first... */
    req=PEM_read_bio_X509_REQ(in, NULL, NULL, NULL);
    /* If that fails, see if it was in ASN1 format */
    if (!req)
        req=d2i_X509_REQ_bio(in, NULL);

    BIO_free_all(in);

    if (!req) {
        printf("Unable to get REQ object from file");
        return NULL;
    }
    return req;
}

X509 *
readCert(const char *fn)
{
    X509 *cert = NULL;
    BIO *in = NULL;

    if (!fn) {
        printf("Filename MUST be supplied\n");
        return NULL;
    }

    in = BIO_new(BIO_s_file());
    if (!in) {
        printf("failed to create a BIO\n");
        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 cert;

    printf("Failed to read certificate file\n");
    return NULL;
}

X509 *signRequestWithCertificate(X509 *issuer, X509_REQ *req, RSA *key)
{
    X509 *cert = NULL;
    X509V3_CTX ctx;
    X509_NAME *xn_req = NULL, *subject = NULL;
    EVP_PKEY *pkey = NULL;
    void *tmp[2];
    int i, rv;
    int remove_nids[] = { NID_commonName, NID_pkcs9_emailAddress, -1 };
    int ignore_nids[] = { NID_countryName, NID_organizationName, 
                          NID_organizationalUnitName, -1 };

    if (!issuer || !req || !key) {
        return NULL;
    }

    cert = X509_new();
    if (!cert) {
        return NULL;
    }

    /* Make it a V3 certificate. */
    if (X509_set_version(cert, 2L) != 1) {
        goto out;
    }

    /* The certificate has 2 X509_NAMEstructures, one for the issuer and one
     * for the certificate (termed the subject). We now create these and set
     * them into the certificate.
     *
     *   1) copy the subject from the issuing certificate
     *   2) delete the CN and emailAddress from the issuer subject
     *   3) add fields from the request that we permit (basically anything other
     *      than CN, O & OU)
     */

    {
        X509_NAME *issuerSubject = X509_get_subject_name(issuer);

        if (! issuerSubject) {
            printf("Unable to get the "
                                        "subject name from the issuer");
            goto out;
        }

        /* Set the issuing certificate subject as the issuer subject for
         * the new certificate.
         */
        if (X509_set_issuer_name(cert, issuerSubject) != 1) {
            printf("Unable to set the issuer "
                                         "name of the new certificate");
//            X509_NAME_free(issuerSubject);
            goto out;
        }

        subject = X509_NAME_dup(issuerSubject);
//        X509_NAME_free(issuerSubject);
        

        if (! subject) {
            printf("Unable to duplicate the "
                                        "subject name from the issuer");
            goto out;
        }
    }

    /* remove some uneeded fields */
    for (i = 0; remove_nids[i] != -1; i++) {
        int pos = -1;
        while ((pos = X509_NAME_get_index_by_NID(subject, 
                remove_nids[i], pos)) != -1) {
            X509_NAME_ENTRY *ne = X509_NAME_delete_entry(subject, pos);
            if (ne)
                X509_NAME_ENTRY_free(ne);
        }
    }

    /* go through the fields present in the request and add those that
     * we need for the final certificate.
     */
    if (!(xn_req = X509_REQ_get_subject_name(req))) {
        printf("Unable to get the "
                                    "subject name from the request");
        goto out;
    }

    for (i = 0; i < X509_NAME_entry_count(xn_req); i++) {
        int j;
        int skip = 0;
        X509_NAME_ENTRY *ne = X509_NAME_get_entry(xn_req, i);
        for (j = 0; ignore_nids[j] != -1; j++) {
            int pos = -1;

            /* The OU may be set in the request, but unless the certificate
             * will be used for signing we want to set the OU from the
             * issuing certificate.
             */
            do {
                pos = X509_NAME_get_index_by_NID(xn_req, ignore_nids[j], pos);
                if (pos == i)
                    skip = 1;
            } while (pos != -1);
        }
        if (skip)
            continue;
        if (!(X509_NAME_add_entry(subject, ne, -1, 0))) {
            printf("Unable to adjust the "
                                 "subject for the new certificate");
            goto out;
        }
    }
//    X509_NAME_free(xn_req);

    if (X509_set_subject_name(cert, subject) != 1) {
        printf("Unable to set the subject "
                                      "name of the new certificate");
        X509_NAME_free(subject);
        goto out;
    }
    X509_NAME_free(subject);

    /* 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) {
        printf("Unable to get the public key "
                "from the supplied request");
        goto out;
    }
    rv = X509_set_pubkey(cert, pkey);
    EVP_PKEY_free(pkey);

    if (rv != 1) {
        printf("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), 0))) {
        printf("Unable to set notBefore time");
        goto out;
    }
    if (!(X509_gmtime_adj(X509_get_notAfter(cert), 36400))) {
        printf("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 */
/*
    X509V3_set_ctx(&ctx, isroot ? cert : ca_cert, cert, NULL, NULL, 0);
    for (i = 0; always_add[i].left; i++) {
        if (certificate_add_extension(cac, cert, &ctx, always_add[i].left,
                                      always_add[i].right) != 0)
            goto out;
    }

    if (cac->ca_extensions) {
        for (i = 0; i < cac->ca_ext_count; i++) {
            if (certificate_add_extension(cac, cert, &ctx, cac->ca_extensions[i].key,
                                          cac->ca_extensions[i].value) != 0)
                goto out;
        }
    }
*/

    /* Claim credit for the creation... */
    certificateAddExtension(cert, &ctx, "nsComment", "Created by BaDCA");

    if (signCertificateWithKey(cert, key) == 0)
        goto out;
printf("cert = %p\n", cert);
    /* All is well, we have a new certificate, so return it! */
    return cert;

    /* below this is used for error cases */
out:
    if (cert)
        X509_free(cert);
    return NULL;
}

