#ifndef CRYPTO_MODULE_H
#define CRYPTO_MODULE_H

#include "debug.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/aes_ccm.h>
#include <openssl/sha.h>
#include <openssl/rsa.h>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/crypto.h>
#ifndef USE_SCRYPTO
#include <openssl/modes/modes_lcl.h>
#else
#include <openssl/evp.h>
#include <openssl/cipher.h>
BIGNUM *EC_POINT_point2bn(const EC_GROUP *, const EC_POINT *,
	point_conversion_form_t form, BIGNUM *, BN_CTX *);
EC_POINT *EC_POINT_bn2point(const EC_GROUP *, const BIGNUM *,
	EC_POINT *, BN_CTX *);
#endif

typedef uint16_t CRYPTO_STATUS;

// CRYPTO Return status
#define CRYPTO_STATUS_SUCCESS                   0x0000
#define CRYPTO_STATUS_FAILED                    0x0001
#define CRYPTO_STATUS_INVALID_ARGUMENT          0x0002

/* All sizes are in bytes */
#define AP_ID_SIZE                              16

#define AES_BLOCK_SIZE                          16
#define AES_128_KEY_SIZE                        16
#define AES_256_KEY_SIZE                        32

#define IV_SIZE                                 16

#define ECC_PRIVKEY_SIZE                        32
#define ECC_PUBKEY_SIZE                         65
#define ECC_SECRET_SIZE                         32

#define ECDSA_SIG_SIZE                          64
#define ECDSA_SIG_WITH_ASN1_SIZE                72

#define RSA_KEY_COMPONENT_SIZE                  256
#define RSA_SIG_SIZE                            256
#define RSA_SIG_WITH_ASN1_SIZE                  277

#define SHA1_DIGEST_SIZE                        20
#define SHA256_DIGEST_SIZE                      32
#define SHA384_DIGEST_SIZE                      48
#define SHA512_DIGEST_SIZE                      64

#define MD_TYPE_NONE                            0
#define MD_TYPE_SHA1                            1
#define MD_TYPE_SHA256                          2
#define MD_TYPE_SHA384                          3
#define MD_TYPE_SHA512                          4


/* ============================= Chip dependency cryptography ============================= */
CRYPTO_STATUS crypto_gen_random( uint8_t *out, uint32_t size );
CRYPTO_STATUS crypto_get_tz_encryption_key( uint8_t out[AES_256_KEY_SIZE] );
/* =============================== RSA cryptography =============================== */
typedef struct rsa_key_st {
    uint8_t modulus[300];
    uint32_t modulus_size;
    uint8_t publicExponent[5];
    uint32_t publicExponent_size;
    uint8_t privateExponent[300];
    uint32_t privateExponent_size;
    uint8_t prime1[150];
    uint32_t prime1_size;
    uint8_t prime2[150];
    uint32_t prime2_size;
    uint8_t exponent1[150];
    uint32_t exponent1_size;
    uint8_t exponent2[150];
    uint32_t exponent2_size;
    uint8_t coefficient[150];
    uint32_t coefficient_size;
} rsa_key_t;

/* Generate rsa keypair */
CRYPTO_STATUS crypto_gen_rsa_key(RSA **rsa_key, rsa_key_t *rsa_key_info);

/* Re-generate rsa key */
CRYPTO_STATUS crypto_regen_rsa_pubkey(RSA **rsa_key, rsa_key_t *rsa_key_info);
CRYPTO_STATUS crypto_regen_rsa_privkey(RSA **rsa_key, rsa_key_t *rsa_key_info);

/* Free resourses at pointers of structure containing rsa key */
void crypto_clear_rsa_key(RSA **rsa_key);

/* RSA  encrypt / decrypt with OAEP padding */
CRYPTO_STATUS crypto_rsa_oaep_encrypt(RSA *rsakey, const uint8_t *in, uint32_t in_size, uint8_t *out, uint32_t out_size);
CRYPTO_STATUS crypto_rsa_oaep_decrypt(RSA *rsakey, const uint8_t *in, uint32_t in_size, uint8_t *out, uint32_t *out_size);

/* RSA  PKCS sign and verify */
CRYPTO_STATUS crypto_rsa_sign_pkcs(uint8_t md_type, uint8_t isHashed, RSA *rsakey, uint8_t *data, uint32_t data_len, uint8_t *sig, uint32_t *sig_len);
CRYPTO_STATUS crypto_rsa_verify_pkcs(uint8_t md_type, uint8_t isHashed, RSA *rsakey, uint8_t *data, uint32_t data_len, uint8_t *sig, uint32_t sig_len);

/* RSA  PSS sign and verify */
CRYPTO_STATUS crypto_rsa_sign_pss(uint8_t md_type, uint8_t isHashed, RSA *rsakey, uint8_t *data, uint32_t data_len, uint8_t *sig, uint32_t *sig_len);
CRYPTO_STATUS crypto_rsa_verify_pss(uint8_t md_type, uint8_t isHashed, RSA *rsakey, uint8_t *data, uint32_t data_len, uint8_t *sig, uint32_t sig_len);

/* =============================== Elliptic curve cryptography =============================== */

typedef struct ecc_key_st {
    uint8_t privkey_binary[ECC_PRIVKEY_SIZE];
    uint32_t privkey_size;

    uint8_t pubkey_binary[ECC_PUBKEY_SIZE];
    uint32_t pubkey_size;
} ecc_key_t;

/* Generate ephimeral elliptic keypair */
CRYPTO_STATUS crypto_gen_ecc_key(EC_KEY **ecc_key, ecc_key_t *ecc_key_info);

/* Re-generate ephimeral elliptic public key */
CRYPTO_STATUS crypto_regen_ecc_pubkey(EC_KEY **ecc_key, uint8_t *pub_key, uint32_t pub_key_size);
CRYPTO_STATUS crypto_regen_ecc_privkey(EC_KEY **ecc_key, uint8_t *priv_key, uint32_t priv_key_size);

/* Free resourses at pointers of structure containing elliptic key */
void crypto_clear_ecc_key(EC_KEY **ecc_key);

/* Perform elliptic Diffie-Hellman algorithm to derrive key from ephimeral private key and peer's public key */
CRYPTO_STATUS crypto_gen_ecc_secret(uint8_t *priv_key, uint32_t priv_key_size, const uint8_t *peer_key, uint32_t peer_key_size, uint8_t *secret);

/* ECDSA sign and verify */
CRYPTO_STATUS crypto_ecdsa_sign(uint8_t md_type, EC_KEY *eckey, uint8_t *in, uint32_t inLen,
                                       uint8_t *sig_r, uint32_t *sig_r_Len, uint8_t *sig_s, uint32_t *sig_s_Len);
CRYPTO_STATUS crypto_ecdsa_verify(uint8_t md_type, EC_KEY *eckey, uint8_t *in, uint32_t inLen,
                                        uint8_t *sig_r, uint32_t sig_r_Len, uint8_t *sig_s, uint32_t sig_s_Len);

/* =============================== AES mode =============================== */

/* Encrypt buffer with AES CBC. Assumes input is already padded */
CRYPTO_STATUS crypto_aes_cbc_encrypt(const uint8_t *in, uint32_t size, uint8_t *out, uint32_t *outSize, 
                                             const uint8_t iv[AES_BLOCK_SIZE], const uint8_t *key, const uint32_t keyLen);

/* Decrypt buffer with AES CBC. Does not remove the padding */
CRYPTO_STATUS crypto_aes_cbc_decrypt(const uint8_t *in, uint32_t size, uint8_t *out, uint32_t *outSize, 
                                             const uint8_t iv[AES_BLOCK_SIZE], const uint8_t *key, const uint32_t keyLen);

/* Remove PKCS#5 padding from decrypted data (function just returns actual data size) */
uint32_t crypto_aes_unpadded_data_size(const uint8_t *data, uint32_t size);

/* =============================== Other crypto functions =============================== */

/* Encrypt buffer with AES-CCM. Assumes input is already padded */
CRYPTO_STATUS crypto_aes_ccm_encrypt(unsigned char *iv,unsigned int ivlen,
                                              unsigned char *key,unsigned int keylen,
                                              unsigned char *aad, unsigned long aadlen,
                                              unsigned char *data,unsigned long datalen,
                                              unsigned char *out, unsigned long *outlen,
                                              unsigned int taglen);

/* Decrypt buffer with AES-CCM. Does not remove the padding */
CRYPTO_STATUS crypto_aes_ccm_decrypt(unsigned char *iv,unsigned int ivlen,
                                              unsigned char *key,unsigned int keylen,
                                              unsigned char *aad, unsigned long aadlen,
                                              unsigned char *data,unsigned long datalen,
                                              unsigned char *out, unsigned long *outlen,
                                              unsigned int taglen);

/* Encrypt buffer with AES-GCM. Assumes input is already padded */
CRYPTO_STATUS crypto_aes_gcm_encrypt(unsigned char *iv,unsigned int ivlen,
                                              unsigned char *key,unsigned int keylen,
                                              unsigned char *aad, unsigned long aadlen,
                                              unsigned char *data,unsigned long datalen,
                                              unsigned char *out, unsigned long *outlen,
                                              unsigned char *tag, int taglen);

/* Decrypt buffer with AES-GCM. Does not remove the padding */
CRYPTO_STATUS crypto_aes_gcm_decrypt(unsigned char *iv,unsigned int ivlen,
                                              unsigned char *key,unsigned int keylen,
                                              unsigned char *aad, unsigned long aadlen,
                                              unsigned char *data,unsigned long datalen,
                                              unsigned char *out, unsigned long *outlen,
                                              unsigned char *tag, int taglen);

/* Additional arguments are pairs { uint8_t*, uint32_t } - blocks of data to be hashed
 * type argument : MD_TYPE_SHA1, MD_TYPE_SHA256, MD_TYPE_SHA384 / Last argument is a NULL pointer */
CRYPTO_STATUS crypto_sha(uint8_t md_type, uint8_t *out, ...);

/* Same syntax as in function above, but with CMAC key added */
CRYPTO_STATUS crypto_aes_cmac(uint8_t out[AES_BLOCK_SIZE], const uint8_t *key, const uint32_t keyLen, ...);

/* Additional arguments are pairs { uint8_t*, uint32_t } - blocks of data to be hmaced with MD_TYPE_SHA1,MD_TYPE_SHA256, MD_TYPE_SHA384
 * Last argument is a NULL pointer */
CRYPTO_STATUS crypto_hmac (uint8_t md_type, uint8_t *out, const uint8_t *key, uint32_t keyLen, ...);

/* Comapare in a constant-time way */
uint32_t crypto_secure_cmp(const uint8_t *mac1, const uint8_t *mac2, const uint32_t macLen);

/* Fill memory with zeros to wipe sensetive data */
void crypto_clear_mem(void *mem, uint8_t size_bytes);

#endif /* CRYPTO_MODULE_H */
