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

SEC_ASN1_MKSUB(SEC_IntegerTemplate)
SEC_ASN1_MKSUB(SEC_ObjectIDTemplate)

const SEC_ASN1Template CERT_DisplayTextTypeTemplate[] = {
    { SEC_ASN1_CHOICE, offsetof(SECItem, type), 0, sizeof(SECItem) },
    { SEC_ASN1_IA5_STRING, 0, 0, siAsciiString },
    { SEC_ASN1_VISIBLE_STRING, 0, 0, siVisibleString },
    { SEC_ASN1_BMP_STRING, 0, 0, siBMPString },
    { SEC_ASN1_UTF8_STRING, 0, 0, siUTF8String },
    { 0 }
};

const SEC_ASN1Template CERT_NoticeReferenceTemplate[] = {
    { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTNoticeReference) },
    { SEC_ASN1_INLINE, offsetof(CERTNoticeReference, organization),
      CERT_DisplayTextTypeTemplate, 0 },
    { SEC_ASN1_SEQUENCE_OF | SEC_ASN1_XTRN,
      offsetof(CERTNoticeReference, noticeNumbers),
      SEC_ASN1_SUB(SEC_IntegerTemplate) },
    { 0 }
};

const SEC_ASN1Template CERT_UserNoticeTemplate[] = {
    { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTUserNotice) },
    { SEC_ASN1_INLINE | SEC_ASN1_OPTIONAL,
      offsetof(CERTUserNotice, noticeReference), CERT_NoticeReferenceTemplate,
      0 },
    { SEC_ASN1_INLINE | SEC_ASN1_OPTIONAL,
      offsetof(CERTUserNotice, displayText), CERT_DisplayTextTypeTemplate, 0 },
    { 0 }
};

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

const SEC_ASN1Template CERT_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),
      CERT_PolicyQualifierTemplate },
    { 0 }
};

const SEC_ASN1Template CERT_CertificatePoliciesTemplate[] = {
    { SEC_ASN1_SEQUENCE_OF, offsetof(CERTCertificatePolicies, policyInfos),
      CERT_PolicyInfoTemplate, sizeof(CERTCertificatePolicies) }
};

const SEC_ASN1Template CERT_PolicyMapTemplate[] = {
    { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTPolicyMap) },
    { SEC_ASN1_OBJECT_ID, offsetof(CERTPolicyMap, issuerDomainPolicy) },
    { SEC_ASN1_OBJECT_ID, offsetof(CERTPolicyMap, subjectDomainPolicy) },
    { 0 }
};

const SEC_ASN1Template CERT_PolicyMappingsTemplate[] = {
    { SEC_ASN1_SEQUENCE_OF, offsetof(CERTCertificatePolicyMappings, policyMaps),
      CERT_PolicyMapTemplate, sizeof(CERTPolicyMap) }
};

const SEC_ASN1Template CERT_PolicyConstraintsTemplate[] = {
    { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTCertificatePolicyConstraints) },
    { SEC_ASN1_OPTIONAL | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0,
      offsetof(CERTCertificatePolicyConstraints, explicitPolicySkipCerts),
      SEC_ASN1_SUB(SEC_IntegerTemplate) },
    { SEC_ASN1_OPTIONAL | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 1,
      offsetof(CERTCertificatePolicyConstraints, inhibitMappingSkipCerts),
      SEC_ASN1_SUB(SEC_IntegerTemplate) },
    { 0 }
};

const SEC_ASN1Template CERT_InhibitAnyTemplate[] = {
    { SEC_ASN1_INTEGER,
      offsetof(CERTCertificateInhibitAny, inhibitAnySkipCerts), NULL,
      sizeof(CERTCertificateInhibitAny) }
};

CERTCertificatePolicies *
CERT_DecodeCertificatePoliciesExtension(const 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 certificate policies structure */
    policies = (CERTCertificatePolicies *)PORT_ArenaZAlloc(
        arena, sizeof(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, CERT_CertificatePoliciesTemplate, &newExtnValue);

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

    /* initialize the oid tags */
    policyInfos = policies->policyInfos;
    while (*policyInfos != NULL) {
        policyInfo = *policyInfos;
        policyInfo->oid = SECOID_FindOIDTag(&policyInfo->policyID);
        policyQualifiers = policyInfo->policyQualifiers;
        while (policyQualifiers != NULL && *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);
}

void
CERT_DestroyCertificatePoliciesExtension(CERTCertificatePolicies *policies)
{
    if (policies != NULL) {
        PORT_FreeArena(policies->arena, PR_FALSE);
    }
    return;
}

CERTCertificatePolicyMappings *
CERT_DecodePolicyMappingsExtension(SECItem *extnValue)
{
    PLArenaPool *arena = NULL;
    SECStatus rv;
    CERTCertificatePolicyMappings *mappings;
    SECItem newExtnValue;

    /* make a new arena */
    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (!arena) {
        goto loser;
    }

    /* allocate the policy mappings structure */
    mappings = (CERTCertificatePolicyMappings *)PORT_ArenaZAlloc(
        arena, sizeof(CERTCertificatePolicyMappings));
    if (mappings == NULL) {
        goto loser;
    }
    mappings->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 mappings */
    rv = SEC_QuickDERDecodeItem(arena, mappings, CERT_PolicyMappingsTemplate,
                                &newExtnValue);
    if (rv != SECSuccess) {
        goto loser;
    }

    return (mappings);

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

    return (NULL);
}

SECStatus
CERT_DestroyPolicyMappingsExtension(CERTCertificatePolicyMappings *mappings)
{
    if (mappings != NULL) {
        PORT_FreeArena(mappings->arena, PR_FALSE);
    }
    return SECSuccess;
}

SECStatus
CERT_DecodePolicyConstraintsExtension(
    CERTCertificatePolicyConstraints *decodedValue, const SECItem *encodedValue)
{
    CERTCertificatePolicyConstraints decodeContext;
    PLArenaPool *arena = NULL;
    SECStatus rv = SECSuccess;

    /* initialize so we can tell when an optional component is omitted */
    PORT_Memset(&decodeContext, 0, sizeof(decodeContext));

    /* make a new arena */
    arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
    if (!arena) {
        return SECFailure;
    }

    do {
        /* decode the policy constraints */
        rv = SEC_QuickDERDecodeItem(arena, &decodeContext,
                                    CERT_PolicyConstraintsTemplate,
                                    encodedValue);

        if (rv != SECSuccess) {
            break;
        }

        if (decodeContext.explicitPolicySkipCerts.len == 0) {
            *(PRInt32 *)decodedValue->explicitPolicySkipCerts.data = -1;
        } else {
            long val;
            PORT_SetError(0);
            val = DER_GetInteger(&decodeContext.explicitPolicySkipCerts);
            if (PORT_GetError() != 0 || val < 0) {
                rv = SECFailure;
                break;
            }
            *(PRInt32 *)decodedValue->explicitPolicySkipCerts.data = (PRInt32)val;
        }

        if (decodeContext.inhibitMappingSkipCerts.len == 0) {
            *(PRInt32 *)decodedValue->inhibitMappingSkipCerts.data = -1;
        } else {
            long val;
            PORT_SetError(0);
            val = DER_GetInteger(&decodeContext.inhibitMappingSkipCerts);
            if (PORT_GetError() != 0 || val < 0) {
                rv = SECFailure;
                break;
            }
            *(PRInt32 *)decodedValue->inhibitMappingSkipCerts.data = (PRInt32)val;
        }

    } while (0);

    PORT_FreeArena(arena, PR_FALSE);
    return (rv);
}

SECStatus
CERT_DecodeInhibitAnyExtension(CERTCertificateInhibitAny *decodedValue,
                               SECItem *encodedValue)
{
    CERTCertificateInhibitAny decodeContext;
    PLArenaPool *arena = NULL;
    SECStatus rv = SECSuccess;

    /* make a new arena */
    arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
    if (!arena) {
        return SECFailure;
    }

    do {

        /* decode the policy mappings */
        decodeContext.inhibitAnySkipCerts.type = siUnsignedInteger;
        rv = SEC_QuickDERDecodeItem(arena, &decodeContext,
                                    CERT_InhibitAnyTemplate, encodedValue);

        if (rv != SECSuccess) {
            break;
        }

        {
            long val;
            PORT_SetError(0);
            val = DER_GetInteger(&decodeContext.inhibitAnySkipCerts);
            if (PORT_GetError() != 0 || val < 0) {
                rv = SECFailure;
                break;
            }
            *(PRInt32 *)decodedValue->inhibitAnySkipCerts.data = (PRInt32)val;
        }

    } while (0);

    PORT_FreeArena(arena, PR_FALSE);
    return (rv);
}

CERTUserNotice *
CERT_DecodeUserNotice(SECItem *noticeItem)
{
    PLArenaPool *arena = NULL;
    SECStatus rv;
    CERTUserNotice *userNotice;
    SECItem newNoticeItem;

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

    if (!arena) {
        goto loser;
    }

    /* allocate the userNotice structure */
    userNotice =
        (CERTUserNotice *)PORT_ArenaZAlloc(arena, sizeof(CERTUserNotice));

    if (userNotice == NULL) {
        goto loser;
    }

    userNotice->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, &newNoticeItem, noticeItem);
    if (rv != SECSuccess) {
        goto loser;
    }

    /* decode the user notice */
    rv = SEC_QuickDERDecodeItem(arena, userNotice, CERT_UserNoticeTemplate,
                                &newNoticeItem);

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

    if (userNotice->derNoticeReference.data != NULL) {

        rv = SEC_QuickDERDecodeItem(arena, &userNotice->noticeReference,
                                    CERT_NoticeReferenceTemplate,
                                    &userNotice->derNoticeReference);
        if (rv == SECFailure) {
            goto loser;
        }
    }

    return (userNotice);

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

    return (NULL);
}

void
CERT_DestroyUserNotice(CERTUserNotice *userNotice)
{
    if (userNotice != NULL) {
        PORT_FreeArena(userNotice->arena, PR_FALSE);
    }
    return;
}

static CERTPolicyStringCallback policyStringCB = NULL;
static void *policyStringCBArg = NULL;

void
CERT_SetCAPolicyStringCallback(CERTPolicyStringCallback cb, void *cbarg)
{
    policyStringCB = cb;
    policyStringCBArg = cbarg;
    return;
}

char *
CERT_GetCertCommentString(CERTCertificate *cert)
{
    return NULL;
}

const SEC_ASN1Template CERT_OidSeqTemplate[] = {
    { SEC_ASN1_SEQUENCE_OF | SEC_ASN1_XTRN, offsetof(CERTOidSequence, oids),
      SEC_ASN1_SUB(SEC_ObjectIDTemplate) }
};

CERTOidSequence *
CERT_DecodeOidSequence(const SECItem *seqItem)
{
    PLArenaPool *arena = NULL;
    SECStatus rv;
    CERTOidSequence *oidSeq;
    SECItem newSeqItem;

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

    if (!arena) {
        goto loser;
    }

    /* allocate the userNotice structure */
    oidSeq =
        (CERTOidSequence *)PORT_ArenaZAlloc(arena, sizeof(CERTOidSequence));

    if (oidSeq == NULL) {
        goto loser;
    }

    oidSeq->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, &newSeqItem, seqItem);
    if (rv != SECSuccess) {
        goto loser;
    }

    /* decode the user notice */
    rv =
        SEC_QuickDERDecodeItem(arena, oidSeq, CERT_OidSeqTemplate, &newSeqItem);

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

    return (oidSeq);

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

void
CERT_DestroyOidSequence(CERTOidSequence *oidSeq)
{
    if (oidSeq != NULL) {
        PORT_FreeArena(oidSeq->arena, PR_FALSE);
    }
    return;
}

PRBool
CERT_GovtApprovedBitSet(CERTCertificate *cert)
{
    SECStatus rv;
    SECItem extItem;
    CERTOidSequence *oidSeq = NULL;
    PRBool ret;
    SECItem **oids;
    SECItem *oid;
    SECOidTag oidTag;

    extItem.data = NULL;
    rv = CERT_FindCertExtension(cert, SEC_OID_X509_EXT_KEY_USAGE, &extItem);
    if (rv != SECSuccess) {
        goto loser;
    }

    oidSeq = CERT_DecodeOidSequence(&extItem);
    if (oidSeq == NULL) {
        goto loser;
    }

    oids = oidSeq->oids;
    while (oids != NULL && *oids != NULL) {
        oid = *oids;

        oidTag = SECOID_FindOIDTag(oid);

        if (oidTag == SEC_OID_NS_KEY_USAGE_GOVT_APPROVED) {
            goto success;
        }

        oids++;
    }

loser:
    ret = PR_FALSE;
    goto done;
success:
    ret = PR_TRUE;
done:
    if (oidSeq != NULL) {
        CERT_DestroyOidSequence(oidSeq);
    }
    if (extItem.data != NULL) {
        PORT_Free(extItem.data);
    }
    return (ret);
}

SECStatus
CERT_EncodePolicyConstraintsExtension(PLArenaPool *arena,
                                      CERTCertificatePolicyConstraints *constr,
                                      SECItem *dest)
{
    SECStatus rv = SECSuccess;

    PORT_Assert(constr != NULL && dest != NULL);
    if (constr == NULL || dest == NULL) {
        return SECFailure;
    }

    if (SEC_ASN1EncodeItem(arena, dest, constr,
                           CERT_PolicyConstraintsTemplate) == NULL) {
        rv = SECFailure;
    }
    return (rv);
}

SECStatus
CERT_EncodePolicyMappingExtension(PLArenaPool *arena,
                                  CERTCertificatePolicyMappings *mapping,
                                  SECItem *dest)
{
    SECStatus rv = SECSuccess;

    PORT_Assert(mapping != NULL && dest != NULL);
    if (mapping == NULL || dest == NULL) {
        return SECFailure;
    }

    if (SEC_ASN1EncodeItem(arena, dest, mapping, CERT_PolicyMappingsTemplate) ==
        NULL) {
        rv = SECFailure;
    }
    return (rv);
}

SECStatus
CERT_EncodeCertPoliciesExtension(PLArenaPool *arena, CERTPolicyInfo **info,
                                 SECItem *dest)
{
    SECStatus rv = SECSuccess;

    PORT_Assert(info != NULL && dest != NULL);
    if (info == NULL || dest == NULL) {
        return SECFailure;
    }

    if (SEC_ASN1EncodeItem(arena, dest, info,
                           CERT_CertificatePoliciesTemplate) == NULL) {
        rv = SECFailure;
    }
    return (rv);
}

SECStatus
CERT_EncodeUserNotice(PLArenaPool *arena, CERTUserNotice *notice, SECItem *dest)
{
    SECStatus rv = SECSuccess;

    PORT_Assert(notice != NULL && dest != NULL);
    if (notice == NULL || dest == NULL) {
        return SECFailure;
    }

    if (SEC_ASN1EncodeItem(arena, dest, notice, CERT_UserNoticeTemplate) ==
        NULL) {
        rv = SECFailure;
    }

    return (rv);
}

SECStatus
CERT_EncodeNoticeReference(PLArenaPool *arena, CERTNoticeReference *reference,
                           SECItem *dest)
{
    SECStatus rv = SECSuccess;

    PORT_Assert(reference != NULL && dest != NULL);
    if (reference == NULL || dest == NULL) {
        return SECFailure;
    }

    if (SEC_ASN1EncodeItem(arena, dest, reference,
                           CERT_NoticeReferenceTemplate) == NULL) {
        rv = SECFailure;
    }

    return (rv);
}

SECStatus
CERT_EncodeInhibitAnyExtension(PLArenaPool *arena,
                               CERTCertificateInhibitAny *certInhibitAny,
                               SECItem *dest)
{
    SECStatus rv = SECSuccess;

    PORT_Assert(certInhibitAny != NULL && dest != NULL);
    if (certInhibitAny == NULL || dest == NULL) {
        return SECFailure;
    }

    if (SEC_ASN1EncodeItem(arena, dest, certInhibitAny,
                           CERT_InhibitAnyTemplate) == NULL) {
        rv = SECFailure;
    }
    return (rv);
}
