/* -*- Mode: C; tab-width: 8 -*-*/
/* 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/. */

#include "crmf.h"
#include "crmfi.h"
#include "keyhi.h"
#include "secder.h"

CRMFPOPChoice
CRMF_CertReqMsgGetPOPType(CRMFCertReqMsg *inCertReqMsg)
{
    PORT_Assert(inCertReqMsg != NULL);
    if (inCertReqMsg != NULL && inCertReqMsg->pop != NULL) {
        return inCertReqMsg->pop->popUsed;
    }
    return crmfNoPOPChoice;
}

static SECStatus
crmf_destroy_validity(CRMFOptionalValidity *inValidity, PRBool freeit)
{
    if (inValidity != NULL) {
        if (inValidity->notBefore.data != NULL) {
            PORT_Free(inValidity->notBefore.data);
        }
        if (inValidity->notAfter.data != NULL) {
            PORT_Free(inValidity->notAfter.data);
        }
        if (freeit) {
            PORT_Free(inValidity);
        }
    }
    return SECSuccess;
}

static SECStatus
crmf_copy_cert_request_validity(PLArenaPool *poolp,
                                CRMFOptionalValidity **destValidity,
                                CRMFOptionalValidity *srcValidity)
{
    CRMFOptionalValidity *myValidity = NULL;
    SECStatus rv;

    *destValidity = myValidity = (poolp == NULL) ? PORT_ZNew(CRMFOptionalValidity)
                                                 : PORT_ArenaZNew(poolp, CRMFOptionalValidity);
    if (myValidity == NULL) {
        goto loser;
    }
    if (srcValidity->notBefore.data != NULL) {
        rv = SECITEM_CopyItem(poolp, &myValidity->notBefore,
                              &srcValidity->notBefore);
        if (rv != SECSuccess) {
            goto loser;
        }
    }
    if (srcValidity->notAfter.data != NULL) {
        rv = SECITEM_CopyItem(poolp, &myValidity->notAfter,
                              &srcValidity->notAfter);
        if (rv != SECSuccess) {
            goto loser;
        }
    }
    return SECSuccess;
loser:
    if (myValidity != NULL && poolp == NULL) {
        crmf_destroy_validity(myValidity, PR_TRUE);
    }
    return SECFailure;
}

static SECStatus
crmf_copy_extensions(PLArenaPool *poolp,
                     CRMFCertTemplate *destTemplate,
                     CRMFCertExtension **srcExt)
{
    int numExt = 0, i;
    CRMFCertExtension **myExtArray = NULL;

    while (srcExt[numExt] != NULL) {
        numExt++;
    }
    if (numExt == 0) {
        /*No extensions to copy.*/
        destTemplate->extensions = NULL;
        destTemplate->numExtensions = 0;
        return SECSuccess;
    }
    destTemplate->extensions = myExtArray =
        PORT_NewArray(CRMFCertExtension *, numExt + 1);
    if (myExtArray == NULL) {
        goto loser;
    }

    for (i = 0; i < numExt; i++) {
        myExtArray[i] = crmf_copy_cert_extension(poolp, srcExt[i]);
        if (myExtArray[i] == NULL) {
            goto loser;
        }
    }
    destTemplate->numExtensions = numExt;
    myExtArray[numExt] = NULL;
    return SECSuccess;
loser:
    if (myExtArray != NULL) {
        if (poolp == NULL) {
            for (i = 0; myExtArray[i] != NULL; i++) {
                CRMF_DestroyCertExtension(myExtArray[i]);
            }
        }
        PORT_Free(myExtArray);
    }
    destTemplate->extensions = NULL;
    destTemplate->numExtensions = 0;
    return SECFailure;
}

static SECStatus
crmf_copy_cert_request_template(PLArenaPool *poolp,
                                CRMFCertTemplate *destTemplate,
                                CRMFCertTemplate *srcTemplate)
{
    SECStatus rv;

    if (srcTemplate->version.data != NULL) {
        rv = SECITEM_CopyItem(poolp, &destTemplate->version,
                              &srcTemplate->version);
        if (rv != SECSuccess) {
            goto loser;
        }
    }
    if (srcTemplate->serialNumber.data != NULL) {
        rv = SECITEM_CopyItem(poolp, &destTemplate->serialNumber,
                              &srcTemplate->serialNumber);
        if (rv != SECSuccess) {
            goto loser;
        }
    }
    if (srcTemplate->signingAlg != NULL) {
        rv = crmf_template_copy_secalg(poolp, &destTemplate->signingAlg,
                                       srcTemplate->signingAlg);
        if (rv != SECSuccess) {
            goto loser;
        }
    }
    if (srcTemplate->issuer != NULL) {
        rv = crmf_copy_cert_name(poolp, &destTemplate->issuer,
                                 srcTemplate->issuer);
        if (rv != SECSuccess) {
            goto loser;
        }
    }
    if (srcTemplate->validity != NULL) {
        rv = crmf_copy_cert_request_validity(poolp, &destTemplate->validity,
                                             srcTemplate->validity);
        if (rv != SECSuccess) {
            goto loser;
        }
    }
    if (srcTemplate->subject != NULL) {
        rv = crmf_copy_cert_name(poolp, &destTemplate->subject,
                                 srcTemplate->subject);
        if (rv != SECSuccess) {
            goto loser;
        }
    }
    if (srcTemplate->publicKey != NULL) {
        rv = crmf_template_add_public_key(poolp, &destTemplate->publicKey,
                                          srcTemplate->publicKey);
        if (rv != SECSuccess) {
            goto loser;
        }
    }
    if (srcTemplate->issuerUID.data != NULL) {
        rv = crmf_make_bitstring_copy(poolp, &destTemplate->issuerUID,
                                      &srcTemplate->issuerUID);
        if (rv != SECSuccess) {
            goto loser;
        }
    }
    if (srcTemplate->subjectUID.data != NULL) {
        rv = crmf_make_bitstring_copy(poolp, &destTemplate->subjectUID,
                                      &srcTemplate->subjectUID);
        if (rv != SECSuccess) {
            goto loser;
        }
    }
    if (srcTemplate->extensions != NULL) {
        rv = crmf_copy_extensions(poolp, destTemplate,
                                  srcTemplate->extensions);
        if (rv != SECSuccess) {
            goto loser;
        }
    }
    return SECSuccess;
loser:
    return SECFailure;
}

static CRMFControl *
crmf_copy_control(PLArenaPool *poolp, CRMFControl *srcControl)
{
    CRMFControl *newControl;
    SECStatus rv;

    newControl = (poolp == NULL) ? PORT_ZNew(CRMFControl) :
                                 PORT_ArenaZNew(poolp, CRMFControl);
    if (newControl == NULL) {
        goto loser;
    }
    newControl->tag = srcControl->tag;
    rv = SECITEM_CopyItem(poolp, &newControl->derTag, &srcControl->derTag);
    if (rv != SECSuccess) {
        goto loser;
    }
    rv = SECITEM_CopyItem(poolp, &newControl->derValue, &srcControl->derValue);
    if (rv != SECSuccess) {
        goto loser;
    }
    /* We only handle PKIArchiveOptions Control right now.  But if in
     * the future, more controls that are part of the union are added,
     * then they need to be handled here as well.
     */
    switch (newControl->tag) {
        case SEC_OID_PKIX_REGCTRL_PKI_ARCH_OPTIONS:
            rv = crmf_copy_pkiarchiveoptions(poolp,
                                             &newControl->value.archiveOptions,
                                             &srcControl->value.archiveOptions);
            break;
        default:
            rv = SECSuccess;
    }
    if (rv != SECSuccess) {
        goto loser;
    }
    return newControl;

loser:
    if (poolp == NULL && newControl != NULL) {
        CRMF_DestroyControl(newControl);
    }
    return NULL;
}

static SECStatus
crmf_copy_cert_request_controls(PLArenaPool *poolp,
                                CRMFCertRequest *destReq,
                                CRMFCertRequest *srcReq)
{
    int numControls, i;
    CRMFControl **myControls = NULL;

    numControls = CRMF_CertRequestGetNumControls(srcReq);
    if (numControls == 0) {
        /* No Controls To Copy*/
        return SECSuccess;
    }
    myControls = destReq->controls = PORT_NewArray(CRMFControl *,
                                                   numControls + 1);
    if (myControls == NULL) {
        goto loser;
    }
    for (i = 0; i < numControls; i++) {
        myControls[i] = crmf_copy_control(poolp, srcReq->controls[i]);
        if (myControls[i] == NULL) {
            goto loser;
        }
    }
    myControls[numControls] = NULL;
    return SECSuccess;
loser:
    if (myControls != NULL) {
        if (poolp == NULL) {
            for (i = 0; myControls[i] != NULL; i++) {
                CRMF_DestroyControl(myControls[i]);
            }
        }
        PORT_Free(myControls);
    }
    return SECFailure;
}

CRMFCertRequest *
crmf_copy_cert_request(PLArenaPool *poolp, CRMFCertRequest *srcReq)
{
    CRMFCertRequest *newReq = NULL;
    SECStatus rv;

    if (srcReq == NULL) {
        return NULL;
    }
    newReq = (poolp == NULL) ? PORT_ZNew(CRMFCertRequest) :
                             PORT_ArenaZNew(poolp, CRMFCertRequest);
    if (newReq == NULL) {
        goto loser;
    }
    rv = SECITEM_CopyItem(poolp, &newReq->certReqId, &srcReq->certReqId);
    if (rv != SECSuccess) {
        goto loser;
    }
    rv = crmf_copy_cert_request_template(poolp, &newReq->certTemplate,
                                         &srcReq->certTemplate);
    if (rv != SECSuccess) {
        goto loser;
    }
    rv = crmf_copy_cert_request_controls(poolp, newReq, srcReq);
    if (rv != SECSuccess) {
        goto loser;
    }
    return newReq;
loser:
    if (newReq != NULL && poolp == NULL) {
        CRMF_DestroyCertRequest(newReq);
        PORT_Free(newReq);
    }
    return NULL;
}

SECStatus
CRMF_DestroyGetValidity(CRMFGetValidity *inValidity)
{
    PORT_Assert(inValidity != NULL);
    if (inValidity != NULL) {
        if (inValidity->notAfter) {
            PORT_Free(inValidity->notAfter);
            inValidity->notAfter = NULL;
        }
        if (inValidity->notBefore) {
            PORT_Free(inValidity->notBefore);
            inValidity->notBefore = NULL;
        }
    }
    return SECSuccess;
}

SECStatus
crmf_make_bitstring_copy(PLArenaPool *arena, SECItem *dest, SECItem *src)
{
    int origLenBits;
    int bytesToCopy;
    SECStatus rv;

    origLenBits = src->len;
    bytesToCopy = CRMF_BITS_TO_BYTES(origLenBits);
    src->len = bytesToCopy;
    rv = SECITEM_CopyItem(arena, dest, src);
    src->len = origLenBits;
    if (rv != SECSuccess) {
        return rv;
    }
    dest->len = origLenBits;
    return SECSuccess;
}

int
CRMF_CertRequestGetNumberOfExtensions(CRMFCertRequest *inCertReq)
{
    CRMFCertTemplate *certTemplate;
    int count = 0;

    certTemplate = &inCertReq->certTemplate;
    if (certTemplate->extensions) {
        while (certTemplate->extensions[count] != NULL)
            count++;
    }
    return count;
}

SECOidTag
CRMF_CertExtensionGetOidTag(CRMFCertExtension *inExtension)
{
    PORT_Assert(inExtension != NULL);
    if (inExtension == NULL) {
        return SEC_OID_UNKNOWN;
    }
    return SECOID_FindOIDTag(&inExtension->id);
}

PRBool
CRMF_CertExtensionGetIsCritical(CRMFCertExtension *inExt)
{
    PORT_Assert(inExt != NULL);
    if (inExt == NULL) {
        return PR_FALSE;
    }
    return inExt->critical.data != NULL;
}

SECItem *
CRMF_CertExtensionGetValue(CRMFCertExtension *inExtension)
{
    PORT_Assert(inExtension != NULL);
    if (inExtension == NULL) {
        return NULL;
    }

    return SECITEM_DupItem(&inExtension->value);
}

SECStatus
CRMF_DestroyPOPOSigningKey(CRMFPOPOSigningKey *inKey)
{
    PORT_Assert(inKey != NULL);
    if (inKey != NULL) {
        if (inKey->derInput.data != NULL) {
            SECITEM_FreeItem(&inKey->derInput, PR_FALSE);
        }
        if (inKey->algorithmIdentifier != NULL) {
            SECOID_DestroyAlgorithmID(inKey->algorithmIdentifier, PR_TRUE);
        }
        if (inKey->signature.data != NULL) {
            SECITEM_FreeItem(&inKey->signature, PR_FALSE);
        }
        PORT_Free(inKey);
    }
    return SECSuccess;
}

SECStatus
CRMF_DestroyPOPOPrivKey(CRMFPOPOPrivKey *inPrivKey)
{
    PORT_Assert(inPrivKey != NULL);
    if (inPrivKey != NULL) {
        SECITEM_FreeItem(&inPrivKey->message.thisMessage, PR_FALSE);
        PORT_Free(inPrivKey);
    }
    return SECSuccess;
}

int
CRMF_CertRequestGetNumControls(CRMFCertRequest *inCertReq)
{
    int count = 0;

    PORT_Assert(inCertReq != NULL);
    if (inCertReq == NULL) {
        return 0;
    }
    if (inCertReq->controls) {
        while (inCertReq->controls[count] != NULL)
            count++;
    }
    return count;
}
