/**
 * \file asn1build.c
 * \brief ASN.1 certificate builder.
 * \author Dmytro Podgornyi (d.podgornyi@samsung.com)
 * \version 0.1
 * \date Created May 28, 2013
 * \par In Samsung Ukraine R&D Center (SURC) under a contract between
 * \par LLC "Samsung Electronics Ukraine Company" (Kiev, Ukraine) and
 * \par "Samsung Elecrtronics Co", Ltd (Seoul, Republic of Korea)
 * \par Copyright: (c) Samsung Electronics Co, Ltd 2012. All rights reserved.
 **/

#include <stdint.h>
#include "asn1build.h"
#include "CommLayerData.h"
#include "secMemoryManager.h"
#include "log.h"

static asn1_gen_t *asn1_build_attr(asn1_object_t *oid, asn1_type_t type,
                                   char *value, size_t value_size)
{
    asn1_gen_t *set = NULL;
    asn1_gen_t *seq = NULL;
    char *copy = NULL;

    set = secMemoryManagerMalloc(sizeof(asn1_gen_t) * 2);
    seq = secMemoryManagerMalloc(sizeof(asn1_gen_t) * 3);
    copy = secMemoryManagerMalloc(value_size + 1);
    if (!set || !seq || !copy) {
        secMemoryManagerFree(set);
        secMemoryManagerFree(seq);
        secMemoryManagerFree(copy);
        return NULL;
    }

    memcpy(copy, value, value_size);
    copy[value_size] = '\0';

    seq[0].type = ASN1_TYPE_OBJECT;
    seq[0].value = (void*)oid;
    seq[1].type = type;
    seq[1].value = (void*)copy;
    seq[2].type = ASN1_TYPE_END;
    seq[2].value = NULL;

    set[0].type = ASN1_TYPE_SEQUENCE;
    set[0].value = (void*)seq;
    set[1].type = ASN1_TYPE_END;
    set[1].value = NULL;

    return set;
}

static asn1_gen_t *asn1_build_ext_helper(asn1_object_t *oid, uint8_t *value,
                                         uint32_t valueLen, int crit)
{
    asn1_gen_t *seq = NULL;
    asn1_gen_t *tmp = NULL;
    asn1_string_t *str = NULL;

    if (crit) {
        seq = secMemoryManagerMalloc(sizeof(asn1_gen_t) * 4);
    } else {
        seq = secMemoryManagerMalloc(sizeof(asn1_gen_t) * 3);
    }

    if (!seq) {
        return NULL;
    }

    tmp = seq;
    tmp->type = ASN1_TYPE_OBJECT;
    tmp->value = oid;
    ++tmp;
    if (crit) {
        tmp->type = ASN1_TYPE_BOOLEAN;
        tmp->value = (void*)0xff;
        ++tmp;
    }

    str = secMemoryManagerMalloc(sizeof(*str));
    if (!str) {
        secMemoryManagerFree(seq);
        return NULL;
    }
    str->size = valueLen;
    str->data = (char*)value;
    str->unused = 0;

    tmp->type = ASN1_TYPE_OCTETSTRING;
    tmp->value = (void*)str;
    ++tmp;

    tmp->type = ASN1_TYPE_END;
    tmp->value = NULL;

    return seq;
}

asn1_gen_t *asn1_build_ext_raw(asn1_object_t *oid, const uint8_t *value,
                                      uint32_t valueLen, int crit)
{
    uint8_t *tmp = NULL;
    asn1_gen_t *res = NULL;

    tmp = secMemoryManagerMalloc(valueLen);
    if (!tmp) {
        return NULL;
    }

    memcpy(tmp, value, valueLen);
    res = asn1_build_ext_helper(oid, tmp, valueLen, crit);
    if (!res) {
        secMemoryManagerFree(tmp);
    }
    return res;
}

asn1_gen_t *asn1_build_ext(asn1_object_t *oid, asn1_gen_t *value,
                                  int crit)
{
    uint32_t len = 0;
    uint8_t *out = NULL;
    int32_t result = X509_INTERNAL_ERROR;
    asn1_gen_t *res = NULL;

    len = asn1_get_field_bytes(value);
    out = secMemoryManagerMalloc(len);
    if (!out) {
        return NULL;
    }
    result = asn1_gen_field(out, len, value);
    if (result != NOT_ERROR) {
        LOGE("asn1_build_ext: incomplete value");
        secMemoryManagerFree(out);
        return NULL;
    }
    res = asn1_build_ext_helper(oid, out, len, crit);
    if (!res) {
        secMemoryManagerFree(out);
    }
    return res;
}

/* free sequence generated by asn1_build_pub_rsa_struct or asn1_build_ext */
void asn1_free_ext(asn1_gen_t *seq)
{
    asn1_gen_t *tmp = seq;

    while (tmp->type != ASN1_TYPE_END) {
        switch (tmp->type) {
        case ASN1_TYPE_BITSTRING:
        case ASN1_TYPE_OCTETSTRING:
            secMemoryManagerFree(((asn1_string_t*)tmp->value)->data);
            secMemoryManagerFree(tmp->value);
            break;

        default:
            break;
        }
        ++tmp;
    }

    secMemoryManagerFree(seq);
}

void asn1_free(asn1_gen_t *seq)
{
    asn1_gen_t *tmp = seq;

    while (tmp->type != ASN1_TYPE_END) {
        switch (tmp->type) {
        case ASN1_TYPE_SET:
        case ASN1_TYPE_EXPL_0:
        case ASN1_TYPE_IMPL_0:
        case ASN1_TYPE_EXPL_1:
        case ASN1_TYPE_EXPL_3:
        case ASN1_TYPE_SEQUENCE:
            asn1_free((asn1_gen_t*)tmp->value);
            break;

        case ASN1_TYPE_UTF8STRING:
        case ASN1_TYPE_PRINTABLE_STRING:
            secMemoryManagerFree(tmp->value);
            break;

        default:
            break;
        }
        ++tmp;
    }

    secMemoryManagerFree(seq);
}

int32_t build_utc_time(char *notbefore, size_t notbefore_len,
                              char *notafter, size_t notafter_len,
                              const struct x509_certificate *cert)
{
    int year = cert->not_before.year;
    int month = cert->not_before.mon;
    int day = cert->not_before.mday;
    int hour = cert->not_before.hour;
    int minute = cert->not_before.min;
    int second = cert->not_before.sec;

    if (notbefore_len < 14 || notafter_len < 14) {
        return WRONG_DATA;
    }

    if (month < 1 || month > 12) return WRONG_DATA;
    if (day < 1 || day > 31) return WRONG_DATA;
    if (hour > 23) return WRONG_DATA;
    if (minute > 59) return WRONG_DATA;
    if (second > 59) return WRONG_DATA;

    notbefore[0]  = '0' + (year/10)%10;
    notbefore[1]  = '0' + year%10;
    notbefore[2]  = '0' + month/10;
    notbefore[3]  = '0' + month%10;    
    notbefore[4]  = '0' + day/10;
    notbefore[5]  = '0' + day%10;
    notbefore[6]  = '0' + hour/10;
    notbefore[7]  = '0' + hour%10;    
    notbefore[8]  = '0' + minute/10;
    notbefore[9]  = '0' + minute%10;
    notbefore[10] = '0' + second/10;
    notbefore[11] = '0' + second%10;    
    notbefore[12] = 'Z';
    notbefore[13] = 0;

    year = cert->not_after.year;
    month = cert->not_after.mon;
    day = cert->not_after.mday;
    hour = cert->not_after.hour;
    minute = cert->not_after.min;
    second = cert->not_after.sec;

    if (month < 1 || month > 12) return WRONG_DATA;
    if (day < 1 || day > 31) return WRONG_DATA;
    if (hour > 23) return WRONG_DATA;
    if (minute > 59) return WRONG_DATA;
    if (second > 59) return WRONG_DATA;

    notafter[0]  = '0' + (year/10)%10;
    notafter[1]  = '0' + year%10;
    notafter[2]  = '0' + month/10;
    notafter[3]  = '0' + month%10;
    notafter[4]  = '0' + day/10;
    notafter[5]  = '0' + day%10;
    notafter[6]  = '0' + hour/10;
    notafter[7]  = '0' + hour%10;
    notafter[8]  = '0' + minute/10;
    notafter[9]  = '0' + minute%10;
    notafter[10] = '0' + second/10;
    notafter[11] = '0' + second%10; 
    notafter[12] = 'Z';
    notafter[13] = 0;

    return NOT_ERROR;
}

asn1_gen_t *asn1_build_name(const struct x509_name *name)
{
    static unsigned long commonNameOID[] = {2, 5, 4, 3};
    static unsigned long countryNameOID[] = {2, 5, 4, 6};
    static unsigned long localityNameOID[] = {2, 5, 4, 7};
    static unsigned long domainComponentOID[] = {0, 9, 2342, 19200300, 100, 1, 25};
    //static unsigned long stateOrProvinceNameOID[] = {2, 5, 4, 8};
    static unsigned long organizationNameOID[] = {2, 5, 4, 10};
    static unsigned long organizationalUnitNameOID[] = {2, 5, 4, 11};
    static unsigned long dnQualifierOID[] = {2, 5, 4, 46};
    static unsigned long uidOID[] = {0, 9, 2342, 19200300, 100, 1, 1};

    static asn1_object_t domainComponent =
        {ARRAY_SIZE(domainComponentOID), domainComponentOID};
    static asn1_object_t commonName =
        {ARRAY_SIZE(commonNameOID), commonNameOID};
    static asn1_object_t countryName =
        {ARRAY_SIZE(countryNameOID), countryNameOID};
    static asn1_object_t localityName =
        {ARRAY_SIZE(localityNameOID), localityNameOID};
    //static asn1_object_t stateOrProvinceName =
    //    {ARRAY_SIZE(stateOrProvinceNameOID), stateOrProvinceNameOID};
    static asn1_object_t organizationName =
        {ARRAY_SIZE(organizationNameOID), organizationNameOID};
    static asn1_object_t organizationalUnitName =
        {ARRAY_SIZE(organizationalUnitNameOID), organizationalUnitNameOID};
    static asn1_object_t dnQualifier =
        {ARRAY_SIZE(dnQualifierOID), dnQualifierOID};
    static asn1_object_t uidObject =
        {ARRAY_SIZE(uidOID), uidOID};


    asn1_gen_t *seq = NULL;
    asn1_gen_t *attr = NULL;
    void *value = NULL;
    size_t value_size = 0;
    int seq_size = 0;
    int i = 0;
    int idx = 0;

    seq_size = name->num_attr + 1;
    seq = secMemoryManagerMalloc(sizeof(asn1_gen_t) * seq_size);
    if (!seq) {
        return NULL;
    }

    for (i = 0; i < seq_size; i++) {
        seq[i].type = ASN1_TYPE_END;
        seq[i].value = NULL;
    }

    for (i = 0; i < name->num_attr; i++) {
        value = (void*)name->attr[i].value;
        value_size = name->attr[i].value_size;

        switch (name->attr[i].type) {
        case X509_NAME_ATTR_DC:
            attr = asn1_build_attr(&domainComponent, ASN1_TYPE_IA5STRING, value,
                                   value_size);
            break;
        case X509_NAME_ATTR_CN:
            attr = asn1_build_attr(&commonName, ASN1_TYPE_UTF8STRING, value,
                                   value_size);
            break;
        case X509_NAME_ATTR_C:
            attr = asn1_build_attr(&countryName, ASN1_TYPE_PRINTABLE_STRING,
                                   value, value_size);
            break;
        case X509_NAME_ATTR_L:
            attr = asn1_build_attr(&localityName, ASN1_TYPE_UTF8STRING, value,
                                   value_size);
            break;
        case X509_NAME_ATTR_O:
            attr = asn1_build_attr(&organizationName,
                                   ASN1_TYPE_UTF8STRING, value, value_size);
            break;
        case X509_NAME_ATTR_OU:
            attr = asn1_build_attr(&organizationalUnitName,
                                   ASN1_TYPE_UTF8STRING, value, value_size);
            break;
        case X509_NAME_ATTR_UID:
            attr = asn1_build_attr(&uidObject, ASN1_TYPE_UTF8STRING, value,
                                   value_size);
            break;
        case X509_NAME_ATTR_DNQUAL:
            attr = asn1_build_attr(&dnQualifier,
                                   ASN1_TYPE_PRINTABLE_STRING, value, value_size);
            break;
        default:
            LOGD("Unknown attribute with id %d", name->attr[i].type);
            attr = NULL;
            break;
        }

        if (!attr) {
            LOGE("Error while generation of attribute with id %d",
                 name->attr[i].type);
            goto err;
        } else {
            if (idx >= (seq_size - 1)) {
                LOGE("Not enough space to set all attributes");
                asn1_free(attr);
                goto err;
            }

            seq[idx].type = ASN1_TYPE_SET;
            seq[idx].value = (void*)attr;
            ++idx;
        }
    }

    /* self-check */
    if (idx >= seq_size || seq[idx].type != ASN1_TYPE_END) {
        LOGE("asn1_build_name() is broken");
        goto err;
    }

    return seq;

err:
    asn1_free(seq);
    return NULL;
}

