/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 *  JARSIGN
 *
 *  Routines used in signing archives.
 */

#include "jar.h"
#include "jarint.h"
#include "secpkcs7.h"
#include "pk11func.h"
#include "sechash.h"

/* from libevent.h */
typedef void (*ETVoidPtrFunc)(void *data);

/* key database wrapper */
/* static SECKEYKeyDBHandle *jar_open_key_database (void); */
/* CHUNQ is our bite size */

#define CHUNQ 64000
#define FILECHUNQ 32768

/*
 *  J A R _ c a l c u l a t e _ d i g e s t
 *
 *  Quick calculation of a digest for
 *  the specified block of memory. Will calculate
 *  for all supported algorithms, now MD5.
 *
 */
JAR_Digest *PR_CALLBACK
JAR_calculate_digest(void *data, long length)
{
    PK11Context *md5 = 0;
    PK11Context *sha1 = 0;
    JAR_Digest *dig = PORT_ZNew(JAR_Digest);
    long chunq;
    unsigned int md5_length, sha1_length;

    if (dig == NULL) {
        /* out of memory allocating digest */
        return NULL;
    }

    md5 = PK11_CreateDigestContext(SEC_OID_MD5);
    if (md5 == NULL) {
        PORT_ZFree(dig, sizeof(JAR_Digest));
        return NULL;
    }
    sha1 = PK11_CreateDigestContext(SEC_OID_SHA1);
    if (sha1 == NULL) {
        PK11_DestroyContext(md5, PR_TRUE);
        /* added due to bug Bug 1250214 - prevent the 2nd memory leak */
        PORT_ZFree(dig, sizeof(JAR_Digest));
        return NULL;
    }

    if (length >= 0) {
        PK11_DigestBegin(md5);
        PK11_DigestBegin(sha1);

        do {
            chunq = length;

            PK11_DigestOp(md5, (unsigned char *)data, chunq);
            PK11_DigestOp(sha1, (unsigned char *)data, chunq);
            length -= chunq;
            data = ((char *)data + chunq);
        } while (length > 0);

        PK11_DigestFinal(md5, dig->md5, &md5_length, MD5_LENGTH);
        PK11_DigestFinal(sha1, dig->sha1, &sha1_length, SHA1_LENGTH);

        PK11_DestroyContext(md5, PR_TRUE);
        PK11_DestroyContext(sha1, PR_TRUE);
    }
    return dig;
}

/*
 *  J A R _ d i g e s t _ f i l e
 *
 *  Calculates the MD5 and SHA1 digests for a file
 *  present on disk, and returns these in JAR_Digest struct.
 *
 */
int
JAR_digest_file(char *filename, JAR_Digest *dig)
{
    JAR_FILE fp;
    PK11Context *md5 = 0;
    PK11Context *sha1 = 0;
    unsigned char *buf = (unsigned char *)PORT_ZAlloc(FILECHUNQ);
    int num;
    unsigned int md5_length, sha1_length;

    if (buf == NULL) {
        /* out of memory */
        return JAR_ERR_MEMORY;
    }

    if ((fp = JAR_FOPEN(filename, "rb")) == 0) {
        /* perror (filename); FIX XXX XXX XXX XXX XXX XXX */
        PORT_Free(buf);
        return JAR_ERR_FNF;
    }

    md5 = PK11_CreateDigestContext(SEC_OID_MD5);
    sha1 = PK11_CreateDigestContext(SEC_OID_SHA1);

    if (md5 == NULL || sha1 == NULL) {
        if (md5) {
            PK11_DestroyContext(md5, PR_TRUE);
        }
        if (sha1) {
            PK11_DestroyContext(sha1, PR_TRUE);
        }
        /* can't generate digest contexts */
        PORT_Free(buf);
        JAR_FCLOSE(fp);
        return JAR_ERR_GENERAL;
    }

    PK11_DigestBegin(md5);
    PK11_DigestBegin(sha1);

    while (1) {
        num = JAR_FREAD(fp, buf, FILECHUNQ);
        if (num == 0)
            break;
        if (num < 0) {
            PK11_DestroyContext(md5, PR_TRUE);
            PK11_DestroyContext(sha1, PR_TRUE);
            PORT_Free(buf);
            JAR_FCLOSE(fp);
            return JAR_ERR_GENERAL;
        }

        PK11_DigestOp(md5, buf, num);
        PK11_DigestOp(sha1, buf, num);
    }

    PK11_DigestFinal(md5, dig->md5, &md5_length, MD5_LENGTH);
    PK11_DigestFinal(sha1, dig->sha1, &sha1_length, SHA1_LENGTH);

    PK11_DestroyContext(md5, PR_TRUE);
    PK11_DestroyContext(sha1, PR_TRUE);

    PORT_Free(buf);
    JAR_FCLOSE(fp);

    return 0;
}

/*
 *  J A R _ o p e n _ k e y _ d a t a b a s e
 *
 */

void *
jar_open_key_database(void)
{
    return NULL;
}

int
jar_close_key_database(void *keydb)
{
    /* We never do close it */
    return 0;
}

/*
 *  j a r _ c r e a t e _ p k 7
 *
 */

static void
jar_pk7_out(void *arg, const char *buf, unsigned long len)
{
    JAR_FWRITE((JAR_FILE)arg, buf, len);
}

int
jar_create_pk7(CERTCertDBHandle *certdb, void *keydb, CERTCertificate *cert,
               char *password, JAR_FILE infp, JAR_FILE outfp)
{
    SEC_PKCS7ContentInfo *cinfo;
    const SECHashObject *hashObj;
    void *mw = NULL;
    void *hashcx;
    unsigned int len;
    int status = 0;
    SECStatus rv;
    SECItem digest;
    unsigned char digestdata[32];
    unsigned char buffer[4096];

    if (outfp == NULL || infp == NULL || cert == NULL)
        return JAR_ERR_GENERAL;

    /* we sign with SHA */
    hashObj = HASH_GetHashObject(HASH_AlgSHA1);

    hashcx = (*hashObj->create)();
    if (hashcx == NULL)
        return JAR_ERR_GENERAL;

    (*hashObj->begin)(hashcx);
    while (1) {
        int nb = JAR_FREAD(infp, buffer, sizeof buffer);
        if (nb == 0) { /* eof */
            break;
        }
        if (nb < 0) {
            (*hashObj->destroy)(hashcx, PR_TRUE);
            return JAR_ERR_GENERAL;
        }
        (*hashObj->update)(hashcx, buffer, nb);
    }
    (*hashObj->end)(hashcx, digestdata, &len, 32);
    (*hashObj->destroy)(hashcx, PR_TRUE);

    digest.data = digestdata;
    digest.len = len;

    /* signtool must use any old context it can find since it's
       calling from inside javaland. */
    PORT_SetError(0);
    cinfo = SEC_PKCS7CreateSignedData(cert, certUsageObjectSigner, NULL,
                                      SEC_OID_SHA1, &digest, NULL, mw);
    if (cinfo == NULL)
        return JAR_ERR_PK7;

    rv = SEC_PKCS7IncludeCertChain(cinfo, NULL);
    if (rv != SECSuccess) {
        status = PORT_GetError();
        SEC_PKCS7DestroyContentInfo(cinfo);
        return status;
    }

    /* Having this here forces signtool to always include signing time. */
    rv = SEC_PKCS7AddSigningTime(cinfo);
    /* don't check error */
    PORT_SetError(0);

    /* if calling from mozilla thread*/
    rv = SEC_PKCS7Encode(cinfo, jar_pk7_out, outfp, NULL, NULL, mw);
    if (rv != SECSuccess)
        status = PORT_GetError();
    SEC_PKCS7DestroyContentInfo(cinfo);
    if (rv != SECSuccess) {
        return ((status < 0) ? status : JAR_ERR_GENERAL);
    }
    return 0;
}
