/**
 * \file asn1build_ec.c
 * \brief EC-specific 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 <string.h>

#include "CommLayerData.h"
#include "log.h"
#include "asn1build.h"
#include "secMemoryManager.h"
#include "objects/obj_mac.h"
#include "cryptoEngine.h"
 
//static unsigned long secp256k1OID[] = {1, 3, 132, 0, 10};
static unsigned long X9_62_prime256v1OID[] = {OBJ_X9_62_prime256v1}; // {1,2,840,10045,3,1,7}
static unsigned long ecPublicKeyOID[] = {OBJ_X9_62_id_ecPublicKey}; //{1, 2, 840, 10045, 2, 1};
//static asn1_object_t secp256k1 =
//    {ARRAY_SIZE(secp256k1OID), secp256k1OID};
static asn1_object_t X9_62_prime256v1 =
    {ARRAY_SIZE(X9_62_prime256v1OID), X9_62_prime256v1OID};

static asn1_object_t ecPublicKey =
    {ARRAY_SIZE(ecPublicKeyOID), ecPublicKeyOID};
    
static asn1_gen_t AlgorithmIdentifierEC[] =
{
    {ASN1_TYPE_OBJECT, &ecPublicKey},
    {ASN1_TYPE_OBJECT, &X9_62_prime256v1},
    {ASN1_TYPE_END, NULL},
};

#define EC_KEY_SIZE ((EC_KEY_BIT_SIZE+7)/8)

static int32_t get_public_key_array(const crypto_t key, uint8_t* out)
{
    uint8_t *x = NULL, *y = NULL;
    uint32_t x_len = 0, y_len = 0;
    uint8_t *offset = out;
    int32_t res = X509_INTERNAL_ERROR;

    if (CRYPTO_EC_get_public_key(key, &x, &x_len, &y, &y_len) != NOT_ERROR)
    {
        LOGE("get_public_key_array: EC_KEY_get_public_key failed");
        goto cleanup;
    }
    /* insert flag, x and y to full-length buffer */
    offset[0] = EC_POINT_CONVERSION_UNCOMPRESSED;
    offset++;
    
    offset += EC_KEY_SIZE - x_len;
    memcpy(offset, x, x_len);
    offset += x_len;
    
    offset += EC_KEY_SIZE - y_len;
    memcpy(offset, y, y_len);

    res = NOT_ERROR;

cleanup:
    if (x != NULL)
        secMemoryManagerFree(x);
    if (y != NULL)
        secMemoryManagerFree(y);
    return res;
}

int32_t asn1_build_pub_ec(const crypto_t key, uint8_t *out, uint32_t *outLen)
{
    asn1_gen_t seq[3] = {ASN1GEN_INIT, ASN1GEN_INIT, ASN1GEN_INIT};
    asn1_string_t str = {0};
    int32_t len = 0;
    /* alocate maximum amount of memory for x, y and 1 byte for uncompressed flag */
    uint8_t public_key[EC_KEY_SIZE * 2 + 1] = {0};
    int32_t res = X509_INTERNAL_ERROR;

    res = get_public_key_array(key, public_key);
    if (res != NOT_ERROR)
    {
        LOGD("Cannot get EC public key");
        return res;
    }

    str.size = sizeof(public_key);
    str.data = (char*)public_key;
    str.unused = 0;

    seq[0].type = ASN1_TYPE_SEQUENCE;
    seq[0].value = (void*)AlgorithmIdentifierEC;
    seq[1].type = ASN1_TYPE_BITSTRING;
    seq[1].value = (void*)&str;
    seq[2].type = ASN1_TYPE_END;
    seq[2].value = NULL;

    res = asn1_validate(seq);
    if (res != NOT_ERROR) {
        LOGE("asn1_build_pub_ec: private key has wrong ASN.1 structure");
        goto cleanup;
    }

    len = asn1_get_field_bytes(seq);
    if (*outLen < len) {
        LOGE("asn1_build_pub_ec: not enough space");
        res = BUFFER_OVERFLOW;
        goto cleanup;
    }
    res = asn1_gen_sequence(out, outLen, seq);
    if (res != NOT_ERROR) {
        LOGE("asn1_build_pri_ec: can't create ASN.1 from private key");
        goto cleanup;
    }
cleanup:
    return res;
}

int32_t asn1_build_pri_ec(uint8_t *out, uint32_t *out_len, const crypto_t ec)
{
    asn1_gen_t a0[2] = {ASN1GEN_INIT, ASN1GEN_INIT};
    asn1_gen_t a1[2] = {ASN1GEN_INIT, ASN1GEN_INIT};
    asn1_gen_t seq[5] = {ASN1GEN_INIT, ASN1GEN_INIT, ASN1GEN_INIT,
                         ASN1GEN_INIT, ASN1GEN_INIT};
    asn1_gen_t prk = ASN1GEN_INIT;
    asn1_string_t prk_str = {0};
    asn1_string_t pbk_str = {0};
    /* alocate maximum amount of memory for x, y and 1 byte for uncompressed flag */
    uint8_t public_key[EC_KEY_SIZE * 2 + 1] = {0};

    uint32_t len = 0;
    int32_t res = X509_INTERNAL_ERROR;

    if (NULL == ec)
    {
        LOGD("asn1_build_pri_ec: ec = NULL");
        return WRONG_DATA;
    }
    prk_str.data = NULL;
    if (CRYPTO_EC_get_private_key(ec, (uint8_t **)&prk_str.data, (uint32_t*)&prk_str.size) != NOT_ERROR)
    {
        LOGD("asn1_build_pri_ec: EC_KEY_get_private_key FAILED");
        return WRONG_DATA;
    }
    prk_str.unused = 0;

    prk.type = ASN1_TYPE_SEQUENCE;
    prk.value = (void*)seq;

    res = get_public_key_array(ec, public_key);
    if (res != NOT_ERROR)
    {
        LOGD("Cannot get EC public key");
        goto cleanup;
    }

    pbk_str.size = sizeof(public_key);
    pbk_str.data = (char*)public_key;
    pbk_str.unused = 0;

    /* version */
    seq[0].type = ASN1_TYPE_LONG;
    seq[0].value = (void*)1;

    seq[1].type = ASN1_TYPE_OCTETSTRING;
    seq[1].value = (void*)&prk_str;

    seq[2].type = ASN1_TYPE_EXPL_0;
    seq[2].value = (void*)a0;

    seq[3].type = ASN1_TYPE_EXPL_1;
    seq[3].value = (void*)a1;

    seq[4].type = ASN1_TYPE_END;
    seq[4].value = NULL;

    a0[0].type = ASN1_TYPE_OBJECT;
    a0[0].value = (void*)&X9_62_prime256v1;
    a0[1].type = ASN1_TYPE_END;
    a0[1].value = NULL;

    a1[0].type = ASN1_TYPE_BITSTRING;
    a1[0].value = &pbk_str;
    a1[1].type = ASN1_TYPE_END;
    a1[1].value = NULL;

    res = asn1_validate(seq);
    if (res != NOT_ERROR) {
        LOGE("asn1_build_pri_ec: private key has wrong ASN.1 structure");
        goto cleanup;
    }

    len = asn1_get_field_bytes(&prk);
    if (*out_len < len) {
        LOGE("asn1_build_pri_ec: not enough space");
        res = BUFFER_OVERFLOW;
        goto cleanup;
    }
    res = asn1_gen_field(out, *out_len, &prk);
    if (res != NOT_ERROR) {
        LOGE("asn1_build_pri_ec: can't create ASN.1 from private key");
        goto cleanup;
    }
    *out_len = len;

    res = NOT_ERROR;

cleanup:
    secMemoryManagerFree(prk_str.data);
    return res;
}
