#ifdef SCP11_ENABLE
#include "crypto_module.h"
#include "SCP11.h"

#include <openssl/bn.h>
#include <openssl/ec.h>
#include <openssl/ecdh.h>
#include <openssl/ecdsa.h>
#include <openssl/obj_mac.h>
#include <openssl/aes.h>
#include <openssl/sha.h>
#include <openssl/rsa.h>
#include <openssl/evp.h>

int32_t scp11_generate_ecc_keypair(sk_ecc_key_t *out1, pk_ecc_key_t *out2) {
    EC_KEY         *keyObject = NULL;
    const EC_GROUP *curve = NULL;
    const EC_POINT *q;
    const BIGNUM   *d;
    int             curve_nid;
    int32_t         ret = -1;

    curve_nid=NID_X9_62_prime256v1;

    keyObject = EC_KEY_new_by_curve_name(curve_nid);
    if (keyObject == NULL) {
	LOGE("scp11_generate_ecc_keypair_EC_GROUP_new_by_curve_name failed!\n");
        goto error;
    }

    if (!EC_KEY_generate_key(keyObject)) {
	LOGE("scp11_generate_ecc_keypair_EC_KEY_generate_key failed!\n");
        goto error;
    }

    curve = EC_KEY_get0_group(keyObject);
    q = EC_KEY_get0_public_key(keyObject);
    d = EC_KEY_get0_private_key(keyObject);
    if (!curve || !q || !d) {
	LOGE("scp11_generate_ecc_keypair_EC_KEY_get0_group failed!\n");
        goto error;
    }

    out2->pk_ecc_key_size = EC_POINT_point2oct(curve,
                                                q,
                                                POINT_CONVERSION_UNCOMPRESSED,
                                                out2->pk_ecc_key,
                                                sizeof(out2->pk_ecc_key),
                                                NULL);
    if (out2->pk_ecc_key_size == 0) {
	LOGE("scp11_generate_ecc_EC_POINT_point2oct failed!\n");
        goto error;
    }

    if ((uint32_t)BN_num_bytes(d) > sizeof(out1->sk_ecc_key)) {
	LOGE("scp11_generate_ecc_BN_num_bytes failed!\n");
        goto error;
    }

    out1->sk_ecc_key_size = BN_bn2bin(d, out1->sk_ecc_key);
    ret = 0;

error:
    if (keyObject != NULL) {
        EC_KEY_free(keyObject);
    }

    return ret;
}

int32_t scp11_shared_secret(uint8_t* pk_a_ecka, uint8_t* sk_a_key, uint8_t* pk_b_ecka, uint32_t pk_b_ecka_len, uint32_t sk_a_key_len, ss_key_t *out) {
    EC_POINT	*q = NULL;
    BIGNUM	*d = NULL;
    EC_KEY	*keyObject = NULL;
    EC_GROUP	*curve = NULL;
    const EC_GROUP	*curve1 = NULL;
    uint8_t	sh[MAX_SCP11_DATA_SIZE];
    int32_t	ret = -1;
    int		curve_nid;
    int		res_len;

    curve_nid = NID_X9_62_prime256v1;

    curve = EC_GROUP_new_by_curve_name(curve_nid);
    if (curve == NULL) {
	LOGE("scp11_shared_secret_EC_GROUP_new_by_curve_name failed!\n");
        goto error;
    }

    q = EC_POINT_new(curve);
    if (q == NULL) {
	LOGE("scp11_shared_secret_EC_POINT_new failed!\n");
        goto error;
    }

    if (!EC_POINT_oct2point(curve, q, pk_a_ecka, pk_b_ecka_len, NULL)) {
	LOGE("scp11_shared_secret_EC_POINT_oct2point failed!\n");
        goto error;
    }

    keyObject = EC_KEY_new();
    if (keyObject == NULL) {
	LOGE("scp11_shared_secret_EC_KEY_new failed!\n");
        goto error;
    }

    if (!EC_KEY_set_group(keyObject, curve)) {
	LOGE("scp11_shared_secret_EC_KEY_set_group failed!\n");
        goto error;
    }

    if (!EC_KEY_set_public_key(keyObject, q)) {
	LOGE("scp11_shared_secret_EC_KEY_set_public_key failed!\n");
        goto error;
    }

    d = BN_bin2bn(sk_a_key, sk_a_key_len, NULL);
    if (d == NULL) {
	LOGE("scp11_shared_secret_BN_bin2bn failed!\n");
        goto error;
    }

    if (!EC_KEY_set_private_key(keyObject, d)) {
	LOGE("scp11_EC_KEY_set_private_key failed!\n");
        goto error;
    }

    curve1 = EC_KEY_get0_group(keyObject);
    q = EC_POINT_new(curve);
    if (q == NULL) {
	LOGE("scp11_shared_secret_EC_POINT_new failed!\n");
        goto error;
    }

    if (!EC_POINT_oct2point( curve1, q, pk_b_ecka, pk_b_ecka_len, NULL)) {
	LOGE("scp11_shared_secret_EC_POINT_oct2point failed!\n");
        goto error;
    }

    if (EC_POINT_is_at_infinity(curve1, q)) {
	LOGE("scp11_shared_secret_EC_POINT_is_at_infinity failed!\n");
        goto error;
    }

    res_len = ECDH_compute_key(&sh, 32, q, keyObject, NULL);
    if (res_len <= 0) {
	LOGE("scp11_shared_secret_ECDH_compute_key failed!\n");
        goto error;
    }
    LOGD("res_len = %d",res_len);
    hex_print_tag_debug("sh", sh, 32);

    out->ss_key_size=32;
    memcpy(out->ss_key,&sh,out->ss_key_size);
    ret = 0;

error:
    if (keyObject != NULL) {
        EC_KEY_free(keyObject);
    }
    if (d != NULL) {
        BN_clear_free(d);
    }
    if (q != NULL) {
        EC_POINT_free(q);
    }
    if (curve != NULL) {
        EC_GROUP_free(curve);
    }
    return ret;
}

int32_t scp11_ecc_key_Object(uint8_t* pk_ca_klcc, uint32_t pk_ca_klcc_len, EC_KEY **out) {
    EC_POINT	*q = NULL;
    BIGNUM	*d = NULL;
    EC_KEY	*keyObject = NULL;
    EC_GROUP	*curve = NULL;
    int32_t	ret = -1;
    int		curve_nid;

    curve_nid = NID_X9_62_prime256v1;

    curve = EC_GROUP_new_by_curve_name(curve_nid);
    if (curve == NULL) {
	LOGE("scp11_ecc_key_Object_EC_GROUP_new_by_curve_name failed!\n");
        goto error;
    }

    q = EC_POINT_new(curve);
    if (q == NULL) {
	LOGE("scp11_ecc_key_Object_EC_POINT_new failed!\n");
        goto error;
    }

    if (!EC_POINT_oct2point(curve, q, pk_ca_klcc, pk_ca_klcc_len, NULL)) {
	LOGE("scp11_ecc_key_Object_EC_POINT_oct2point failed!\n");
        goto error;
    }

    keyObject = EC_KEY_new();
    if (keyObject == NULL) {
	LOGE("scp11_ecc_key_Object_EC_KEY_new failed!\n");
        goto error;
    }

    if (!EC_KEY_set_group(keyObject, curve)) {
	LOGE("scp11_ecc_key_Object_EC_KEY_set_group failed!\n");
        goto error;
    }

    if (!EC_KEY_set_public_key(keyObject, q)) {
	LOGE("scp11_ecc_key_Object_EC_KEY_set_public_key failed!\n");
        goto error;
    }

    *out = keyObject;
    keyObject = NULL;
    ret = 0;

error:
    if (keyObject != NULL) {
        EC_KEY_free(keyObject);
    }
    if (d != NULL) {
        BN_clear_free(d);
    }
    if (q != NULL) {
        EC_POINT_free(q);
    }
    if (curve != NULL) {
        EC_GROUP_free(curve);
    }
    return ret;
}

int32_t scp11_verify_signature(verify_signature_t input) {
    EC_KEY	*keyObject = NULL;
    ECDSA_SIG	bn_signature;
    uint8_t	*r_ptr;
    uint8_t	*s_ptr;
    uint32_t	r_len;
    uint32_t	s_len;

    ESESTATUS status;

    bn_signature.r = NULL;
    bn_signature.s = NULL;

    hex_print_tag_debug("digest", input.digest, input.digest_len);
    hex_print_tag_debug("signature", input.signature, input.signature_len);
    hex_print_tag_debug("pk_ca_klcc", input.pk_ca_klcc, input.pk_ca_klcc_len);

    r_ptr = input.signature;
    r_len = input.signature_len/2;
    s_len = input.signature_len/2;

    if (r_len + s_len != input.signature_len/2) {
        if (0 == r_ptr[0]) {
            r_len++;
        } else {
           s_len++;
        }
    }

    s_ptr = r_ptr + r_len;

    bn_signature.r = BN_bin2bn((const unsigned char *)r_ptr, input.signature_len/2, bn_signature.r);
    bn_signature.s = BN_bin2bn((const unsigned char *)s_ptr, input.signature_len/2, bn_signature.s);

    status = scp11_ecc_key_Object(input.pk_ca_klcc, input.pk_ca_klcc_len, &keyObject);
    if (0 != status) {
	LOGE("scp11_ecc_key_Object() failed!\n");
        return SCP11_CRYPTO_ECC_KEY_OBJECT_FAIL;
    }

    status = ECDSA_do_verify(input.digest, input.digest_len, &bn_signature, keyObject);
    if (1 != status) {
	LOGE("scp11_ECDSA_do_verify() failed!\n");
        return SCP11_CRYPTO_ECDSA_DO_VERIFY_FAIL;
    }

    return SCP11_SUCCESS;

}
#endif

