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

/*
 * Support for various policy related extensions
 */

#include "seccomon.h"
#include "secport.h"
#include "secder.h"
#include "cert.h"
#include "secoid.h"
#include "secasn1.h"
#include "secerr.h"
#include "nspr.h"
#include "secutil.h"

/* This implementation is derived from the one in nss/lib/certdb/policyxtn.c .
** The chief difference is the addition of the OPTIONAL flag to many
** parts.  The idea is to be able to parse and print as much of the
** policy extension as possible, even if some parts are invalid.
**
** If this approach still is unable to decode policy extensions that
** contain invalid parts, then the next approach will be to parse
** the PolicyInfos as a SEQUENCE of ANYs, and then parse each of them
** as PolicyInfos, with the PolicyQualifiers being ANYs, and finally
** parse each of the PolicyQualifiers.
*/

static const SEC_ASN1Template secu_PolicyQualifierTemplate[] = {
    { SEC_ASN1_SEQUENCE,
      0, NULL, sizeof(CERTPolicyQualifier) },
    { SEC_ASN1_OBJECT_ID,
      offsetof(CERTPolicyQualifier, qualifierID) },
    { SEC_ASN1_ANY | SEC_ASN1_OPTIONAL,
      offsetof(CERTPolicyQualifier, qualifierValue) },
    { 0 }
};

static const SEC_ASN1Template secu_PolicyInfoTemplate[] = {
    { SEC_ASN1_SEQUENCE,
      0, NULL, sizeof(CERTPolicyInfo) },
    { SEC_ASN1_OBJECT_ID,
      offsetof(CERTPolicyInfo, policyID) },
    { SEC_ASN1_SEQUENCE_OF | SEC_ASN1_OPTIONAL,
      offsetof(CERTPolicyInfo, policyQualifiers),
      secu_PolicyQualifierTemplate },
    { 0 }
};

static const SEC_ASN1Template secu_CertificatePoliciesTemplate[] = {
    { SEC_ASN1_SEQUENCE_OF,
      offsetof(CERTCertificatePolicies, policyInfos),
      secu_PolicyInfoTemplate, sizeof(CERTCertificatePolicies) }
};

static CERTCertificatePolicies *
secu_DecodeCertificatePoliciesExtension(SECItem *extnValue)
{
    PLArenaPool *arena = NULL;
    SECStatus rv;
    CERTCertificatePolicies *policies;
    CERTPolicyInfo **policyInfos, *policyInfo;
    CERTPolicyQualifier **policyQualifiers, *policyQualifier;
    SECItem newExtnValue;

    /* make a new arena */
    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);

    if (!arena) {
        goto loser;
    }

    /* allocate the certifiate policies structure */
    policies = PORT_ArenaZNew(arena, CERTCertificatePolicies);
    if (policies == NULL) {
        goto loser;
    }

    policies->arena = arena;

    /* copy the DER into the arena, since Quick DER returns data that points
       into the DER input, which may get freed by the caller */
    rv = SECITEM_CopyItem(arena, &newExtnValue, extnValue);
    if (rv != SECSuccess) {
        goto loser;
    }

    /* decode the policy info */
    rv = SEC_QuickDERDecodeItem(arena, policies,
                                secu_CertificatePoliciesTemplate,
                                &newExtnValue);

    if (rv != SECSuccess) {
        goto loser;
    }

    /* initialize the oid tags */
    policyInfos = policies->policyInfos;
    while (policyInfos != NULL && *policyInfos != NULL) {
        policyInfo = *policyInfos;
        policyInfo->oid = SECOID_FindOIDTag(&policyInfo->policyID);
        policyQualifiers = policyInfo->policyQualifiers;
        while (policyQualifiers && *policyQualifiers != NULL) {
            policyQualifier = *policyQualifiers;
            policyQualifier->oid =
                SECOID_FindOIDTag(&policyQualifier->qualifierID);
            policyQualifiers++;
        }
        policyInfos++;
    }

    return (policies);

loser:
    if (arena != NULL) {
        PORT_FreeArena(arena, PR_FALSE);
    }

    return (NULL);
}

static char *
itemToString(SECItem *item)
{
    char *string;

    string = PORT_ZAlloc(item->len + 1);
    if (string == NULL)
        return NULL;
    PORT_Memcpy(string, item->data, item->len);
    string[item->len] = 0;
    return string;
}

static SECStatus
secu_PrintUserNoticeQualifier(FILE *out, SECItem *qualifierValue,
                              char *msg, int level)
{
    CERTUserNotice *userNotice = NULL;
    if (qualifierValue)
        userNotice = CERT_DecodeUserNotice(qualifierValue);
    if (userNotice) {
        if (userNotice->noticeReference.organization.len != 0) {
            char *string =
                itemToString(&userNotice->noticeReference.organization);
            SECItem **itemList = userNotice->noticeReference.noticeNumbers;

            while (itemList && *itemList) {
                SECU_PrintInteger(out, *itemList, string, level + 1);
                itemList++;
            }
            PORT_Free(string);
        }
        if (userNotice->displayText.len != 0) {
            SECU_PrintString(out, &userNotice->displayText,
                             "Display Text", level + 1);
        }
        CERT_DestroyUserNotice(userNotice);
        return SECSuccess;
    }
    return SECFailure; /* caller will print this value */
}

static SECStatus
secu_PrintPolicyQualifier(FILE *out, CERTPolicyQualifier *policyQualifier,
                          char *msg, int level)
{
    SECStatus rv;
    SECItem *qualifierValue = &policyQualifier->qualifierValue;

    SECU_PrintObjectID(out, &policyQualifier->qualifierID,
                       "Policy Qualifier Name", level);
    if (!qualifierValue->data) {
        SECU_Indent(out, level);
        fprintf(out, "Error: missing qualifier\n");
    } else
        switch (policyQualifier->oid) {
            case SEC_OID_PKIX_USER_NOTICE_QUALIFIER:
                rv = secu_PrintUserNoticeQualifier(out, qualifierValue, msg, level);
                if (SECSuccess == rv)
                    break;
            /* fall through on error */
            case SEC_OID_PKIX_CPS_POINTER_QUALIFIER:
            default:
                SECU_PrintAny(out, qualifierValue, "Policy Qualifier Data", level);
                break;
        }
    return SECSuccess;
}

static SECStatus
secu_PrintPolicyInfo(FILE *out, CERTPolicyInfo *policyInfo, char *msg, int level)
{
    CERTPolicyQualifier **policyQualifiers;

    policyQualifiers = policyInfo->policyQualifiers;
    SECU_PrintObjectID(out, &policyInfo->policyID, "Policy Name", level);

    while (policyQualifiers && *policyQualifiers != NULL) {
        secu_PrintPolicyQualifier(out, *policyQualifiers, "", level + 1);
        policyQualifiers++;
    }
    return SECSuccess;
}

void
SECU_PrintPolicy(FILE *out, SECItem *value, char *msg, int level)
{
    CERTCertificatePolicies *policies = NULL;
    CERTPolicyInfo **policyInfos;

    if (msg) {
        SECU_Indent(out, level);
        fprintf(out, "%s: \n", msg);
        level++;
    }
    policies = secu_DecodeCertificatePoliciesExtension(value);
    if (policies == NULL) {
        SECU_PrintAny(out, value, "Invalid Policy Data", level);
        return;
    }

    policyInfos = policies->policyInfos;
    while (policyInfos && *policyInfos != NULL) {
        secu_PrintPolicyInfo(out, *policyInfos, "", level);
        policyInfos++;
    }

    CERT_DestroyCertificatePoliciesExtension(policies);
}

void
SECU_PrintPrivKeyUsagePeriodExtension(FILE *out, SECItem *value,
                                      char *msg, int level)
{
    CERTPrivKeyUsagePeriod *prd;
    PLArenaPool *arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);

    if (!arena) {
        goto loser;
    }
    prd = CERT_DecodePrivKeyUsagePeriodExtension(arena, value);
    if (!prd) {
        goto loser;
    }
    if (prd->notBefore.data) {
        SECU_PrintGeneralizedTime(out, &prd->notBefore, "Not Before", level);
    }
    if (prd->notAfter.data) {
        SECU_PrintGeneralizedTime(out, &prd->notAfter, "Not After ", level);
    }
    if (!prd->notBefore.data && !prd->notAfter.data) {
        SECU_Indent(out, level);
        fprintf(out, "Error: notBefore or notAfter MUST be present.\n");
    loser:
        SECU_PrintAny(out, value, msg, level);
    }
    if (arena) {
        PORT_FreeArena(arena, PR_FALSE);
    }
}