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

/*
 * X.509 Extension Encoding
 */

#include "prtypes.h"
#include "seccomon.h"
#include "secdert.h"
#include "secoidt.h"
#include "secasn1t.h"
#include "secasn1.h"
#include "cert.h"
#include "secder.h"
#include "prprf.h"
#include "xconst.h"
#include "genname.h"
#include "secasn1.h"
#include "secerr.h"

static const SEC_ASN1Template CERTSubjectKeyIDTemplate[] = {
    { SEC_ASN1_OCTET_STRING }
};

static const SEC_ASN1Template CERTIA5TypeTemplate[] = {
    { SEC_ASN1_IA5_STRING }
};

SEC_ASN1_MKSUB(SEC_GeneralizedTimeTemplate)

static const SEC_ASN1Template CERTPrivateKeyUsagePeriodTemplate[] = {
    { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTPrivKeyUsagePeriod) },
    { SEC_ASN1_OPTIONAL | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0,
      offsetof(CERTPrivKeyUsagePeriod, notBefore),
      SEC_ASN1_SUB(SEC_GeneralizedTimeTemplate) },
    { SEC_ASN1_OPTIONAL | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 1,
      offsetof(CERTPrivKeyUsagePeriod, notAfter),
      SEC_ASN1_SUB(SEC_GeneralizedTimeTemplate) },
    { 0 }
};

const SEC_ASN1Template CERTAltNameTemplate[] = {
    { SEC_ASN1_CONSTRUCTED, offsetof(CERTAltNameEncodedContext, encodedGenName),
      CERT_GeneralNamesTemplate }
};

const SEC_ASN1Template CERTAuthInfoAccessItemTemplate[] = {
    { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTAuthInfoAccess) },
    { SEC_ASN1_OBJECT_ID, offsetof(CERTAuthInfoAccess, method) },
    { SEC_ASN1_ANY, offsetof(CERTAuthInfoAccess, derLocation) },
    { 0 }
};

const SEC_ASN1Template CERTAuthInfoAccessTemplate[] = {
    { SEC_ASN1_SEQUENCE_OF, 0, CERTAuthInfoAccessItemTemplate }
};

SECStatus
CERT_EncodeSubjectKeyID(PLArenaPool *arena, const SECItem *srcString,
                        SECItem *encodedValue)
{
    SECStatus rv = SECSuccess;

    if (!srcString) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }
    if (SEC_ASN1EncodeItem(arena, encodedValue, srcString,
                           CERTSubjectKeyIDTemplate) == NULL) {
        rv = SECFailure;
    }

    return (rv);
}

SECStatus
CERT_EncodePrivateKeyUsagePeriod(PLArenaPool *arena,
                                 CERTPrivKeyUsagePeriod *pkup,
                                 SECItem *encodedValue)
{
    SECStatus rv = SECSuccess;

    if (SEC_ASN1EncodeItem(arena, encodedValue, pkup,
                           CERTPrivateKeyUsagePeriodTemplate) == NULL) {
        rv = SECFailure;
    }
    return (rv);
}

CERTPrivKeyUsagePeriod *
CERT_DecodePrivKeyUsagePeriodExtension(PLArenaPool *arena, SECItem *extnValue)
{
    SECStatus rv;
    CERTPrivKeyUsagePeriod *pPeriod;
    SECItem newExtnValue;

    /* allocate the certificate policies structure */
    pPeriod = PORT_ArenaZNew(arena, CERTPrivKeyUsagePeriod);
    if (pPeriod == NULL) {
        goto loser;
    }

    pPeriod->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;
    }

    rv = SEC_QuickDERDecodeItem(
        arena, pPeriod, CERTPrivateKeyUsagePeriodTemplate, &newExtnValue);
    if (rv != SECSuccess) {
        goto loser;
    }
    return pPeriod;

loser:
    return NULL;
}

SECStatus
CERT_EncodeIA5TypeExtension(PLArenaPool *arena, char *value,
                            SECItem *encodedValue)
{
    SECItem encodeContext;
    SECStatus rv = SECSuccess;

    PORT_Memset(&encodeContext, 0, sizeof(encodeContext));

    if (value != NULL) {
        encodeContext.data = (unsigned char *)value;
        encodeContext.len = strlen(value);
    }
    if (SEC_ASN1EncodeItem(arena, encodedValue, &encodeContext,
                           CERTIA5TypeTemplate) == NULL) {
        rv = SECFailure;
    }

    return (rv);
}

SECStatus
CERT_EncodeAltNameExtension(PLArenaPool *arena, CERTGeneralName *value,
                            SECItem *encodedValue)
{
    SECItem **encodedGenName;
    SECStatus rv = SECSuccess;

    encodedGenName = cert_EncodeGeneralNames(arena, value);
    if (SEC_ASN1EncodeItem(arena, encodedValue, &encodedGenName,
                           CERT_GeneralNamesTemplate) == NULL) {
        rv = SECFailure;
    }

    return rv;
}

CERTGeneralName *
CERT_DecodeAltNameExtension(PLArenaPool *reqArena, SECItem *EncodedAltName)
{
    SECStatus rv = SECSuccess;
    CERTAltNameEncodedContext encodedContext;
    SECItem *newEncodedAltName;

    if (!reqArena) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return NULL;
    }

    newEncodedAltName = SECITEM_ArenaDupItem(reqArena, EncodedAltName);
    if (!newEncodedAltName) {
        return NULL;
    }

    encodedContext.encodedGenName = NULL;
    PORT_Memset(&encodedContext, 0, sizeof(CERTAltNameEncodedContext));
    rv = SEC_QuickDERDecodeItem(reqArena, &encodedContext,
                                CERT_GeneralNamesTemplate, newEncodedAltName);
    if (rv == SECFailure) {
        goto loser;
    }
    if (encodedContext.encodedGenName && encodedContext.encodedGenName[0])
        return cert_DecodeGeneralNames(reqArena, encodedContext.encodedGenName);
    /* Extension contained an empty GeneralNames sequence */
    /* Treat as extension not found */
    PORT_SetError(SEC_ERROR_EXTENSION_NOT_FOUND);
loser:
    return NULL;
}

SECStatus
CERT_EncodeNameConstraintsExtension(PLArenaPool *arena,
                                    CERTNameConstraints *value,
                                    SECItem *encodedValue)
{
    SECStatus rv = SECSuccess;

    rv = cert_EncodeNameConstraints(value, arena, encodedValue);
    return rv;
}

CERTNameConstraints *
CERT_DecodeNameConstraintsExtension(PLArenaPool *arena,
                                    const SECItem *encodedConstraints)
{
    return cert_DecodeNameConstraints(arena, encodedConstraints);
}

CERTAuthInfoAccess **
CERT_DecodeAuthInfoAccessExtension(PLArenaPool *reqArena,
                                   const SECItem *encodedExtension)
{
    CERTAuthInfoAccess **info = NULL;
    SECStatus rv;
    int i;
    SECItem *newEncodedExtension;

    if (!reqArena) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return NULL;
    }

    newEncodedExtension = SECITEM_ArenaDupItem(reqArena, encodedExtension);
    if (!newEncodedExtension) {
        return NULL;
    }

    rv = SEC_QuickDERDecodeItem(reqArena, &info, CERTAuthInfoAccessTemplate,
                                newEncodedExtension);
    if (rv != SECSuccess || info == NULL) {
        return NULL;
    }

    for (i = 0; info[i] != NULL; i++) {
        info[i]->location =
            CERT_DecodeGeneralName(reqArena, &(info[i]->derLocation), NULL);
    }
    return info;
}

SECStatus
CERT_EncodeInfoAccessExtension(PLArenaPool *arena, CERTAuthInfoAccess **info,
                               SECItem *dest)
{
    SECItem *dummy;
    int i;

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

    for (i = 0; info[i] != NULL; i++) {
        if (CERT_EncodeGeneralName(info[i]->location, &(info[i]->derLocation),
                                   arena) == NULL)
            /* Note that this may leave some of the locations filled in. */
            return SECFailure;
    }
    dummy = SEC_ASN1EncodeItem(arena, dest, &info, CERTAuthInfoAccessTemplate);
    if (dummy == NULL) {
        return SECFailure;
    }
    return SECSuccess;
}
