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

/*
 * Code for dealing with X509.V3 extensions.
 */

#include "cert.h"
#include "secitem.h"
#include "secoid.h"
#include "secder.h"
#include "secasn1.h"
#include "certxutl.h"
#include "secerr.h"

SECStatus
CERT_FindCertExtensionByOID(CERTCertificate *cert, SECItem *oid, SECItem *value)
{
    return (cert_FindExtensionByOID(cert->extensions, oid, value));
}

SECStatus
CERT_FindCertExtension(const CERTCertificate *cert, int tag, SECItem *value)
{
    return (cert_FindExtension(cert->extensions, tag, value));
}

static void
SetExts(void *object, CERTCertExtension **exts)
{
    CERTCertificate *cert = (CERTCertificate *)object;

    cert->extensions = exts;
    DER_SetUInteger(cert->arena, &(cert->version), SEC_CERTIFICATE_VERSION_3);
}

void *
CERT_StartCertExtensions(CERTCertificate *cert)
{
    return (cert_StartExtensions((void *)cert, cert->arena, SetExts));
}

/*
 * get the value of the Netscape Certificate Type Extension
 */
SECStatus
CERT_FindNSCertTypeExtension(CERTCertificate *cert, SECItem *retItem)
{

    return (CERT_FindBitStringExtension(
        cert->extensions, SEC_OID_NS_CERT_EXT_CERT_TYPE, retItem));
}

/*
 * get the value of a string type extension
 */
char *
CERT_FindNSStringExtension(CERTCertificate *cert, int oidtag)
{
    SECItem wrapperItem, tmpItem = { siBuffer, 0 };
    SECStatus rv;
    PLArenaPool *arena = NULL;
    char *retstring = NULL;

    wrapperItem.data = NULL;
    tmpItem.data = NULL;

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);

    if (!arena) {
        goto loser;
    }

    rv = cert_FindExtension(cert->extensions, oidtag, &wrapperItem);
    if (rv != SECSuccess) {
        goto loser;
    }

    rv = SEC_QuickDERDecodeItem(
        arena, &tmpItem, SEC_ASN1_GET(SEC_IA5StringTemplate), &wrapperItem);

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

    retstring = (char *)PORT_Alloc(tmpItem.len + 1);
    if (retstring == NULL) {
        goto loser;
    }

    PORT_Memcpy(retstring, tmpItem.data, tmpItem.len);
    retstring[tmpItem.len] = '\0';

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

    if (wrapperItem.data) {
        PORT_Free(wrapperItem.data);
    }

    return (retstring);
}

/*
 * get the value of the X.509 v3 Key Usage Extension
 */
SECStatus
CERT_FindKeyUsageExtension(CERTCertificate *cert, SECItem *retItem)
{

    return (CERT_FindBitStringExtension(cert->extensions,
                                        SEC_OID_X509_KEY_USAGE, retItem));
}

/*
 * get the value of the X.509 v3 Key Usage Extension
 */
SECStatus
CERT_FindSubjectKeyIDExtension(CERTCertificate *cert, SECItem *retItem)
{

    SECStatus rv;
    SECItem encodedValue = { siBuffer, NULL, 0 };
    SECItem decodedValue = { siBuffer, NULL, 0 };

    rv = cert_FindExtension(cert->extensions, SEC_OID_X509_SUBJECT_KEY_ID,
                            &encodedValue);
    if (rv == SECSuccess) {
        PLArenaPool *tmpArena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
        if (tmpArena) {
            rv = SEC_QuickDERDecodeItem(tmpArena, &decodedValue,
                                        SEC_ASN1_GET(SEC_OctetStringTemplate),
                                        &encodedValue);
            if (rv == SECSuccess) {
                rv = SECITEM_CopyItem(NULL, retItem, &decodedValue);
            }
            PORT_FreeArena(tmpArena, PR_FALSE);
        } else {
            rv = SECFailure;
        }
    }
    SECITEM_FreeItem(&encodedValue, PR_FALSE);
    return rv;
}

SECStatus
CERT_FindBasicConstraintExten(CERTCertificate *cert,
                              CERTBasicConstraints *value)
{
    SECItem encodedExtenValue;
    SECStatus rv;

    encodedExtenValue.data = NULL;
    encodedExtenValue.len = 0;

    rv = cert_FindExtension(cert->extensions, SEC_OID_X509_BASIC_CONSTRAINTS,
                            &encodedExtenValue);
    if (rv != SECSuccess) {
        return (rv);
    }

    rv = CERT_DecodeBasicConstraintValue(value, &encodedExtenValue);

    /* free the raw extension data */
    PORT_Free(encodedExtenValue.data);
    encodedExtenValue.data = NULL;

    return (rv);
}

CERTAuthKeyID *
CERT_FindAuthKeyIDExten(PLArenaPool *arena, CERTCertificate *cert)
{
    SECItem encodedExtenValue;
    SECStatus rv;
    CERTAuthKeyID *ret;

    encodedExtenValue.data = NULL;
    encodedExtenValue.len = 0;

    rv = cert_FindExtension(cert->extensions, SEC_OID_X509_AUTH_KEY_ID,
                            &encodedExtenValue);
    if (rv != SECSuccess) {
        return (NULL);
    }

    ret = CERT_DecodeAuthKeyID(arena, &encodedExtenValue);

    PORT_Free(encodedExtenValue.data);
    encodedExtenValue.data = NULL;

    return (ret);
}

SECStatus
CERT_CheckCertUsage(CERTCertificate *cert, unsigned char usage)
{
    SECItem keyUsage;
    SECStatus rv;

    /* There is no extension, v1 or v2 certificate */
    if (cert->extensions == NULL) {
        return (SECSuccess);
    }

    keyUsage.data = NULL;

    /* This code formerly ignored the Key Usage extension if it was
    ** marked non-critical.  That was wrong.  Since we do understand it,
    ** we are obligated to honor it, whether or not it is critical.
    */
    rv = CERT_FindKeyUsageExtension(cert, &keyUsage);
    if (rv == SECFailure) {
        rv = (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND) ? SECSuccess
                                                                : SECFailure;
    } else if (!(keyUsage.data[0] & usage)) {
        PORT_SetError(SEC_ERROR_CERT_USAGES_INVALID);
        rv = SECFailure;
    }
    PORT_Free(keyUsage.data);
    return (rv);
}
