/* -*- 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 "cmmf.h"
#include "cmmfi.h"
#include "secitem.h"
#include "secder.h"

SECStatus
cmmf_DestroyPKIStatusInfo(CMMFPKIStatusInfo *info, PRBool freeit)
{
    if (info->status.data != NULL) {
        PORT_Free(info->status.data);
        info->status.data = NULL;
    }
    if (info->statusString.data != NULL) {
        PORT_Free(info->statusString.data);
        info->statusString.data = NULL;
    }
    if (info->failInfo.data != NULL) {
        PORT_Free(info->failInfo.data);
        info->failInfo.data = NULL;
    }
    if (freeit) {
        PORT_Free(info);
    }
    return SECSuccess;
}

SECStatus
CMMF_DestroyCertResponse(CMMFCertResponse *inCertResp)
{
    PORT_Assert(inCertResp != NULL);
    if (inCertResp != NULL) {
        if (inCertResp->certReqId.data != NULL) {
            PORT_Free(inCertResp->certReqId.data);
        }
        cmmf_DestroyPKIStatusInfo(&inCertResp->status, PR_FALSE);
        if (inCertResp->certifiedKeyPair != NULL) {
            CMMF_DestroyCertifiedKeyPair(inCertResp->certifiedKeyPair);
        }
        PORT_Free(inCertResp);
    }
    return SECSuccess;
}

SECStatus
CMMF_DestroyCertRepContent(CMMFCertRepContent *inCertRepContent)
{
    PORT_Assert(inCertRepContent != NULL);
    if (inCertRepContent != NULL) {
        CMMFCertResponse **pResponse = inCertRepContent->response;
        if (pResponse != NULL) {
            for (; *pResponse != NULL; pResponse++) {
                CMMFCertifiedKeyPair *certKeyPair = (*pResponse)->certifiedKeyPair;
                /* XXX Why not call CMMF_DestroyCertifiedKeyPair or
                 ** XXX cmmf_DestroyCertOrEncCert ?
                 */
                if (certKeyPair != NULL &&
                    certKeyPair->certOrEncCert.choice == cmmfCertificate &&
                    certKeyPair->certOrEncCert.cert.certificate != NULL) {
                    CERT_DestroyCertificate(certKeyPair->certOrEncCert.cert.certificate);
                    certKeyPair->certOrEncCert.cert.certificate = NULL;
                }
            }
        }
        if (inCertRepContent->caPubs) {
            CERTCertificate **caPubs = inCertRepContent->caPubs;
            for (; *caPubs; ++caPubs) {
                CERT_DestroyCertificate(*caPubs);
                *caPubs = NULL;
            }
        }
        if (inCertRepContent->poolp != NULL) {
            PORT_FreeArena(inCertRepContent->poolp, PR_TRUE);
        }
    }
    return SECSuccess;
}

SECStatus
CMMF_DestroyPOPODecKeyChallContent(CMMFPOPODecKeyChallContent *inDecKeyCont)
{
    PORT_Assert(inDecKeyCont != NULL);
    if (inDecKeyCont != NULL && inDecKeyCont->poolp) {
        PORT_FreeArena(inDecKeyCont->poolp, PR_FALSE);
    }
    return SECSuccess;
}

SECStatus
crmf_create_prtime(SECItem *src, PRTime **dest)
{
    *dest = PORT_ZNew(PRTime);
    return DER_DecodeTimeChoice(*dest, src);
}

CRMFCertExtension *
crmf_copy_cert_extension(PLArenaPool *poolp, CRMFCertExtension *inExtension)
{
    PRBool isCritical;
    SECOidTag id;
    SECItem *data;
    CRMFCertExtension *newExt;

    PORT_Assert(inExtension != NULL);
    if (inExtension == NULL) {
        return NULL;
    }
    id = CRMF_CertExtensionGetOidTag(inExtension);
    isCritical = CRMF_CertExtensionGetIsCritical(inExtension);
    data = CRMF_CertExtensionGetValue(inExtension);
    newExt = crmf_create_cert_extension(poolp, id,
                                        isCritical,
                                        data);
    SECITEM_FreeItem(data, PR_TRUE);
    return newExt;
}

static SECItem *
cmmf_encode_certificate(CERTCertificate *inCert)
{
    return SEC_ASN1EncodeItem(NULL, NULL, inCert,
                              SEC_ASN1_GET(SEC_SignedCertificateTemplate));
}

CERTCertList *
cmmf_MakeCertList(CERTCertificate **inCerts)
{
    CERTCertList *certList;
    CERTCertificate *currCert;
    SECItem *derCert, *freeCert = NULL;
    SECStatus rv;
    int i;

    certList = CERT_NewCertList();
    if (certList == NULL) {
        return NULL;
    }
    for (i = 0; inCerts[i] != NULL; i++) {
        derCert = &inCerts[i]->derCert;
        if (derCert->data == NULL) {
            derCert = freeCert = cmmf_encode_certificate(inCerts[i]);
        }
        currCert = CERT_NewTempCertificate(CERT_GetDefaultCertDB(),
                                           derCert, NULL, PR_FALSE, PR_TRUE);
        if (freeCert != NULL) {
            SECITEM_FreeItem(freeCert, PR_TRUE);
            freeCert = NULL;
        }
        if (currCert == NULL) {
            goto loser;
        }
        rv = CERT_AddCertToListTail(certList, currCert);
        if (rv != SECSuccess) {
            goto loser;
        }
    }
    return certList;
loser:
    CERT_DestroyCertList(certList);
    return NULL;
}

CMMFPKIStatus
cmmf_PKIStatusInfoGetStatus(CMMFPKIStatusInfo *inStatus)
{
    long derVal;

    derVal = DER_GetInteger(&inStatus->status);
    if (derVal == -1 || derVal < cmmfGranted || derVal >= cmmfNumPKIStatus) {
        return cmmfNoPKIStatus;
    }
    return (CMMFPKIStatus)derVal;
}

int
CMMF_CertRepContentGetNumResponses(CMMFCertRepContent *inCertRepContent)
{
    int numResponses = 0;
    PORT_Assert(inCertRepContent != NULL);
    if (inCertRepContent != NULL && inCertRepContent->response != NULL) {
        while (inCertRepContent->response[numResponses] != NULL) {
            numResponses++;
        }
    }
    return numResponses;
}

SECStatus
cmmf_DestroyCertOrEncCert(CMMFCertOrEncCert *certOrEncCert, PRBool freeit)
{
    switch (certOrEncCert->choice) {
        case cmmfCertificate:
            CERT_DestroyCertificate(certOrEncCert->cert.certificate);
            certOrEncCert->cert.certificate = NULL;
            break;
        case cmmfEncryptedCert:
            crmf_destroy_encrypted_value(certOrEncCert->cert.encryptedCert,
                                         PR_TRUE);
            certOrEncCert->cert.encryptedCert = NULL;
            break;
        default:
            break;
    }
    if (freeit) {
        PORT_Free(certOrEncCert);
    }
    return SECSuccess;
}

SECStatus
cmmf_copy_secitem(PLArenaPool *poolp, SECItem *dest, SECItem *src)
{
    SECStatus rv;

    if (src->data != NULL) {
        rv = SECITEM_CopyItem(poolp, dest, src);
    } else {
        dest->data = NULL;
        dest->len = 0;
        rv = SECSuccess;
    }
    return rv;
}

SECStatus
CMMF_DestroyCertifiedKeyPair(CMMFCertifiedKeyPair *inCertKeyPair)
{
    PORT_Assert(inCertKeyPair != NULL);
    if (inCertKeyPair != NULL) {
        cmmf_DestroyCertOrEncCert(&inCertKeyPair->certOrEncCert, PR_FALSE);
        if (inCertKeyPair->privateKey) {
            crmf_destroy_encrypted_value(inCertKeyPair->privateKey, PR_TRUE);
        }
        if (inCertKeyPair->derPublicationInfo.data) {
            PORT_Free(inCertKeyPair->derPublicationInfo.data);
        }
        PORT_Free(inCertKeyPair);
    }
    return SECSuccess;
}

SECStatus
cmmf_CopyCertResponse(PLArenaPool *poolp,
                      CMMFCertResponse *dest,
                      CMMFCertResponse *src)
{
    SECStatus rv;

    if (src->certReqId.data != NULL) {
        rv = SECITEM_CopyItem(poolp, &dest->certReqId, &src->certReqId);
        if (rv != SECSuccess) {
            return rv;
        }
    }
    rv = cmmf_CopyPKIStatusInfo(poolp, &dest->status, &src->status);
    if (rv != SECSuccess) {
        return rv;
    }
    if (src->certifiedKeyPair != NULL) {
        CMMFCertifiedKeyPair *destKeyPair;

        destKeyPair = (poolp == NULL) ? PORT_ZNew(CMMFCertifiedKeyPair) : PORT_ArenaZNew(poolp, CMMFCertifiedKeyPair);
        if (!destKeyPair) {
            return SECFailure;
        }
        rv = cmmf_CopyCertifiedKeyPair(poolp, destKeyPair,
                                       src->certifiedKeyPair);
        if (rv != SECSuccess) {
            if (!poolp) {
                CMMF_DestroyCertifiedKeyPair(destKeyPair);
            }
            return rv;
        }
        dest->certifiedKeyPair = destKeyPair;
    }
    return SECSuccess;
}

static SECStatus
cmmf_CopyCertOrEncCert(PLArenaPool *poolp, CMMFCertOrEncCert *dest,
                       CMMFCertOrEncCert *src)
{
    SECStatus rv = SECSuccess;
    CRMFEncryptedValue *encVal;

    dest->choice = src->choice;
    rv = cmmf_copy_secitem(poolp, &dest->derValue, &src->derValue);
    switch (src->choice) {
        case cmmfCertificate:
            dest->cert.certificate = CERT_DupCertificate(src->cert.certificate);
            break;
        case cmmfEncryptedCert:
            encVal = (poolp == NULL) ? PORT_ZNew(CRMFEncryptedValue) : PORT_ArenaZNew(poolp, CRMFEncryptedValue);
            if (encVal == NULL) {
                return SECFailure;
            }
            rv = crmf_copy_encryptedvalue(poolp, src->cert.encryptedCert, encVal);
            if (rv != SECSuccess) {
                if (!poolp) {
                    crmf_destroy_encrypted_value(encVal, PR_TRUE);
                }
                return rv;
            }
            dest->cert.encryptedCert = encVal;
            break;
        default:
            rv = SECFailure;
    }
    return rv;
}

SECStatus
cmmf_CopyCertifiedKeyPair(PLArenaPool *poolp, CMMFCertifiedKeyPair *dest,
                          CMMFCertifiedKeyPair *src)
{
    SECStatus rv;

    rv = cmmf_CopyCertOrEncCert(poolp, &dest->certOrEncCert,
                                &src->certOrEncCert);
    if (rv != SECSuccess) {
        return rv;
    }

    if (src->privateKey != NULL) {
        CRMFEncryptedValue *encVal;

        encVal = (poolp == NULL) ? PORT_ZNew(CRMFEncryptedValue) : PORT_ArenaZNew(poolp, CRMFEncryptedValue);
        if (encVal == NULL) {
            return SECFailure;
        }
        rv = crmf_copy_encryptedvalue(poolp, src->privateKey,
                                      encVal);
        if (rv != SECSuccess) {
            if (!poolp) {
                crmf_destroy_encrypted_value(encVal, PR_TRUE);
            }
            return rv;
        }
        dest->privateKey = encVal;
    }
    rv = cmmf_copy_secitem(poolp, &dest->derPublicationInfo,
                           &src->derPublicationInfo);
    return rv;
}

SECStatus
cmmf_CopyPKIStatusInfo(PLArenaPool *poolp, CMMFPKIStatusInfo *dest,
                       CMMFPKIStatusInfo *src)
{
    SECStatus rv;

    rv = cmmf_copy_secitem(poolp, &dest->status, &src->status);
    if (rv != SECSuccess) {
        return rv;
    }
    rv = cmmf_copy_secitem(poolp, &dest->statusString, &src->statusString);
    if (rv != SECSuccess) {
        return rv;
    }
    rv = cmmf_copy_secitem(poolp, &dest->failInfo, &src->failInfo);
    return rv;
}

CERTCertificate *
cmmf_CertOrEncCertGetCertificate(CMMFCertOrEncCert *certOrEncCert,
                                 CERTCertDBHandle *certdb)
{
    if (certOrEncCert->choice != cmmfCertificate ||
        certOrEncCert->cert.certificate == NULL) {
        return NULL;
    }
    return CERT_NewTempCertificate(certdb,
                                   &certOrEncCert->cert.certificate->derCert,
                                   NULL, PR_FALSE, PR_TRUE);
}

SECStatus
cmmf_PKIStatusInfoSetStatus(CMMFPKIStatusInfo *statusInfo,
                            PLArenaPool *poolp,
                            CMMFPKIStatus inStatus)
{
    SECItem *dummy;

    if (inStatus < cmmfGranted || inStatus >= cmmfNumPKIStatus) {
        return SECFailure;
    }

    dummy = SEC_ASN1EncodeInteger(poolp, &statusInfo->status, inStatus);
    PORT_Assert(dummy == &statusInfo->status);
    if (dummy != &statusInfo->status) {
        SECITEM_FreeItem(dummy, PR_TRUE);
        return SECFailure;
    }
    return SECSuccess;
}