/*
 * Copyright (c) 2020 Not for Radio, LLC
 *
 * See the file LICENSE in this distribution.
 *
 */
/*
 * This file contains support for X.509v3 extensions that use
 * AttributesSyntax.
 */
/* vim: set ts=4 sw=4 et: */

#include <stdio.h>
#include "internal/cryptlib.h"
#include <openssl/asn1t.h>
#include <openssl/conf.h>
#include <openssl/ets_crypto.h>
#include <openssl/x509v3.h>
#include "ext_dat.h"

static STACK_OF(CONF_VALUE) *i2v_X509_ATTRIBUTES(X509V3_EXT_METHOD *method,
                                                 X509_ATTRIBUTES *attrs,
                                                 STACK_OF(CONF_VALUE) *ret);
static STACK_OF(CONF_VALUE) *i2v_X509_ATTRIBUTE(X509V3_EXT_METHOD *method,
                                                X509_ATTRIBUTE *attr,
                                                STACK_OF(CONF_VALUE) *ret);
static X509_ATTRIBUTES *v2i_attributes(X509V3_EXT_METHOD *method,
                                       X509V3_CTX *ctx,
                                       STACK_OF(CONF_VALUE) *nval);

const X509V3_EXT_METHOD v3_atts[1] = {
    {NID_associated_information, 0, ASN1_ITEM_ref(X509_ATTRIBUTES),
     0, 0, 0, 0,
     0, 0,
     (X509V3_EXT_I2V)i2v_X509_ATTRIBUTES,
     (X509V3_EXT_V2I)v2i_attributes,
     NULL, NULL, NULL},
};

static STACK_OF(CONF_VALUE) *
i2v_X509_ATTRIBUTES(X509V3_EXT_METHOD *method,
                    X509_ATTRIBUTES *attrs,
                    STACK_OF(CONF_VALUE) *ret)
{
    int i;
    X509_ATTRIBUTE *attr;

    for (i = 0; i < sk_X509_ATTRIBUTE_num(attrs); i++) {
        attr = sk_X509_ATTRIBUTE_value(attrs, i);
        ret = i2v_X509_ATTRIBUTE(method, attr, ret);
    }
    if (!ret)
        return sk_CONF_VALUE_new_null();
    return ret;
}

static STACK_OF(CONF_VALUE) *
i2v_X509_ATTRIBUTE(X509V3_EXT_METHOD *method,
                   X509_ATTRIBUTE *attr,
                   STACK_OF(CONF_VALUE) *ret)
{
    ASN1_OBJECT *obj;
    BIO *btmp;
    char *p;
    int i;

    obj = X509_ATTRIBUTE_get0_object(attr);
    switch (OBJ_obj2nid(obj)) {
    case NID_etsVisibility:
        btmp = BIO_new(BIO_s_mem());
        if (btmp == NULL)
            return NULL;

		for (i = 0; i < X509_ATTRIBUTE_count(attr); i++) {
			ETS_VISIBILITY_INFORMATION *vi;

            if (i > 0)
                BIO_printf(btmp, ",");

            vi = ASN1_TYPE_unpack_sequence(ASN1_ITEM_rptr(ETS_VISIBILITY_INFORMATION),
                                           X509_ATTRIBUTE_get0_type(attr, i));
            if (vi == NULL || i2a_ETS_VISIBILITY_INFORMATION(btmp, vi) < 0)
                BIO_printf(btmp, "<error> %p", (void *)vi);
        }
        BIO_printf(btmp, "%c", '\0');
		BIO_get_mem_data(btmp, &p);
        if (!X509V3_add_value("ETSVisibilityInformation", p, &ret))
            ret = NULL;
        BIO_free(btmp);
        break;
    default:
        if (!X509V3_add_value("<unknown>", "<unsupported>", &ret))
            return NULL;
        break;
    }
    return ret;
}

static X509_ATTRIBUTES *
v2i_attributes(X509V3_EXT_METHOD *method,
               X509V3_CTX *ctx,
               STACK_OF(CONF_VALUE) *nval)
{
    char *objname;
    X509_ATTRIBUTES *attrs;
    X509_ATTRIBUTE *attr;
    CONF_VALUE *cnf;
    ASN1_TYPE *asn1_type;
    STACK_OF(ASN1_TYPE) *sk;
    const int num = sk_CONF_VALUE_num(nval);
    int i;

    attrs = sk_X509_ATTRIBUTE_new_reserve(NULL, num);
    if (attrs == NULL) {
        X509V3err(X509V3_F_V2I_ATTRIBUTES, ERR_R_MALLOC_FAILURE);
        sk_X509_ATTRIBUTE_free(attrs);
        return NULL;
    }

    objname = NULL;
    attr = NULL;
    asn1_type = NULL;
    sk = NULL;
    for (i = 0; i < num; i++) {
        char *cnf_name, *cnf_value;
        char *p;
        const unsigned char *pbuf;
        int objname_len;

        cnf = sk_CONF_VALUE_value(nval, i);
        cnf_name = cnf->name;
        cnf_value = cnf->value;

        if (!cnf_value) {
            X509V3err(X509V3_F_V2I_ATTRIBUTES, X509V3_R_MISSING_VALUE);
            goto err;
        }

        if (name_cmp(cnf_name, "attribute")) {
            X509V3err(X509V3_F_V2I_ATTRIBUTES, X509V3_R_UNSUPPORTED_OPTION);
            ERR_add_error_data(ERR_LIB_X509V3, "name=", cnf_name);
            goto err;
        }

        if ((p = strchr(cnf_value, ';')) == NULL) {
            X509V3err(X509V3_F_V2I_ATTRIBUTES, X509V3_R_INVALID_SYNTAX);
            goto err;
        }

        objname_len = p - cnf_value;
        objname = OPENSSL_strndup(cnf_value, objname_len);
        if (objname == NULL) {
            X509V3err(X509V3_F_V2I_ATTRIBUTES, ERR_R_MALLOC_FAILURE);
            goto err;
        }

        asn1_type = ASN1_generate_v3(p + 1, ctx);
        if (asn1_type == NULL) {
            X509V3err(X509V3_F_V2I_ATTRIBUTES, X509V3_R_INVALID_SYNTAX);
            goto err;
        }

        /*
         * If all is well, then the result of ASN1_generate_v3() is the DER
         * encoding of a set of values of the type of the given object name.
         * We no not perform any validation of the value types.
         */
        if (ASN1_TYPE_get(asn1_type) != V_ASN1_SET) {
            X509V3err(X509V3_F_V2I_ATTRIBUTES, X509V3_R_INVALID_SYNTAX);
            goto err;
        }

        /*
         * Using the temporary is essential as d2i_* will adjust the buffer
         * pointer that is passed to it.
         */
        pbuf = asn1_type->value.asn1_string->data;
        sk = d2i_ASN1_SET_ANY(NULL, &pbuf, asn1_type->value.asn1_string->length);
        if (sk == NULL) {
            X509V3err(X509V3_F_V2I_ATTRIBUTES, X509V3_R_INVALID_SYNTAX);
            goto err;
        }
        ASN1_TYPE_free(asn1_type);
        asn1_type = NULL;

        /*
         * Create an attribute for the given object name with an empty set
         * of values.
         */
        attr = X509_ATTRIBUTE_create_by_txt(NULL, objname, 0, NULL, -1);
        if (attr == NULL) {
            X509V3err(X509V3_F_V2I_ATTRIBUTES, ERR_R_MALLOC_FAILURE);
            goto err;
        }
        OPENSSL_free(objname);
        objname = NULL;

        /*
         * Add all of the values to the attribute's value set.
         */
        while ((asn1_type = sk_ASN1_TYPE_shift(sk)) != NULL) {
            if (!X509_ATTRIBUTE_set1_type(attr, asn1_type)) {
                X509V3err(X509V3_F_V2I_ATTRIBUTES, ERR_R_MALLOC_FAILURE);
                goto err;
            }
        }
        asn1_type = NULL;

        sk_X509_ATTRIBUTE_push(attrs, attr); /* no failure as it was reserved */
    }
    return attrs;
err:
    OPENSSL_free(objname);
    ASN1_TYPE_free(asn1_type);
    sk_ASN1_TYPE_pop_free(sk, ASN1_TYPE_free);
    X509_ATTRIBUTE_free(attr);
    sk_X509_ATTRIBUTE_pop_free(attrs, X509_ATTRIBUTE_free);
    return NULL;
}

/* Local Variables:       */
/* c-basic-offset: 4      */
/* tab-width: 4           */
/* indent-tabs-mode: nil  */
/* End:                   */
