/*!
 * \file      security_ecc.hh
 * \brief     Header file for Elliptic Curve Cryptography.
 * \author    ETSI STF525
 * \copyright ETSI Copyright Notification
 *            No part may be reproduced except as authorized by written permission.
 *            The copyright and the foregoing restriction extend to reproduction in all media.
 *            All rights reserved.
 * \version   0.1
 */
#pragma once

#include <vector>

#include <openssl/err.h>
#include <openssl/ec.h>
#include <openssl/pem.h>
#include <openssl/bn.h>

/*!
 * \enum Supported Elliptic curves
 */
enum class ec_elliptic_curves: unsigned char { // TODO Renamed into ecc_elliptic_curves
  nist_p_256,         /*!< NIST P-256, P-256, primve256v1 */
  brainpool_p_256_r1, /*!< Brainpool P256r1 */
  brainpool_p_384_r1  /*!< Brainpool P384r1 */
}; // End of class ecc_elliptic_curves

/*!
 * \enum Public key coordinates compression mode
 */
enum class ecc_compressed_mode: unsigned char {
  compressed_y_0, /*!< The last significant bit of Y-coodinate ended with 0 */
  compressed_y_1  /*!< The last significant bit of Y-coodinate ended with 1 */
}; // End of class ecc_compressed_mode

/*!
 * \enum Supported encryption algorithem
 */
enum class encryption_algotithm: unsigned char { // TODO Renamed into ecc_encryption_algotithm
  aes_128_ccm,
  aes_256_ccm,
  aes_128_gcm,
  aes_256_gcm
}; // End of class ecc_encryption_algotithm

/*!
 * \class security_ecc
 * \brief This class implements the generation of a key pair private/public
 */
class security_ecc {
  ec_elliptic_curves _elliptic_curve;         /*!< Selected elleptic curve */
  encryption_algotithm _encryption_algotithm; /*!< Selected encryption algotithm */
  EC_KEY* _ec_key;                            /*!< EC_KEY reference */
  const EC_GROUP* _ec_group;                  /*!< EC_GROUP reference */
  BN_CTX* _bn_ctx;                            /*!< Pre-alocated memory used to increase OpenSSL processing */
  std::vector<unsigned char> _pri_key;        /*!< Private key storage */
  std::vector<unsigned char> _pub_key_x;      /*!< Public key X-coordinate storage */
  std::vector<unsigned char> _pub_key_y;      /*!< Public key Y-coordinate storage */
  std::vector<unsigned char> _pub_key_compressed; /*!< Public key compressed */
  ecc_compressed_mode _pub_key_compressed_mode;   /*!< Public key coordinates compression mode */
  std::vector<unsigned char> _secret_key;     /*!< Shared secret key generated by ECIES encryption method */
  std::vector<unsigned char> _enc_key_x;      /*!< Ephemeral public key X-coordinate storage */
  std::vector<unsigned char> _enc_key_y;      /*!< Ephemeral public key Y-coordinate storage */
  std::vector<unsigned char> _sym_key;        /*!< AES symmetric encryption key generated by encryption method */
  std::vector<unsigned char> _enc_sym_key;    /*!< Encrypted AES symmetric encryption key generated by encryption method */
  std::vector<unsigned char> _nonce;          /*!< Initial Vector generated by encryption method */
  std::vector<unsigned char> _tag;            /*!< Tag vector generated by encryption method */
  
public: //! \publicsection
  /*!
   * \brief Default constructor
   * \param[in] p_elliptic_curve The ECDSA curve family to be used
   * \remark Call the method security_ecc::generate() to initialise the private/public key pair
   */ 
  security_ecc(const ec_elliptic_curves p_elliptic_curve);
  /*!
   * \brief Constructor based on the private key only
   * \param[in] p_elliptic_curve The ECDSA curve family to be used
   * \param[in] p_private_key The private key
   * \remark Some public key are created based on the provided private keys
   */ 
  security_ecc(const ec_elliptic_curves p_elliptic_curve, const std::vector<unsigned char>& p_private_key);
  /*!
   * \brief Constructor based on the public keys only
   * \param[in] p_elliptic_curve The ECDSA curve family to be used
   * \remark The call to the method security_ecc::sign() will failed
   * \remark The call to the method security_ecc::generate() will overwrite the provided public keys
   */ 
  security_ecc(const ec_elliptic_curves p_elliptic_curve, const std::vector<unsigned char>& p_public_key_x, const std::vector<unsigned char>& p_public_key_y);
  /*!
   * \brief Constructor based on the public xompressed key only
   * \param[in] p_elliptic_curve The ECDSA curve family to be used
   * \remark The call to the method security_ecc::sign() will failed
   * \remark The call to the method security_ecc::generate() will overwrite the provided public keys
   */ 
  security_ecc(const ec_elliptic_curves p_elliptic_curve, const std::vector<unsigned char>& p_public_key_compressed, const ecc_compressed_mode p_compressed_mode);
  /*!
   * \brief Destructor
   */ 
  virtual ~security_ecc();

  /*!
   * \fn int generate();
   * \brief Generate a pair (PrivateK, PublicK) of keys for signature or encryption
   * \return 0 on success, -1 otherwise
   * \remark To get the generated keys, \see private_key, public_key_x and public_key_y methods
   */ 
  int generate();
  /*!
   * \fn int sign(const std::vector<unsigned char>& p_data, std::vector<unsigned char>& p_r_sig, std::vector<unsigned char>& p_s_sig);
   * \brief Signed the data using ECDSA algorithm
   * \param[in] p_data The data to be signed
   * \param[out] p_r_sig Part of the signature
   * \param[out] p_s_sig Part of the signature
   * \return 0 on success, -1 otherwise
   */ 
  int sign(const std::vector<unsigned char>& p_data, std::vector<unsigned char>& p_r_sig, std::vector<unsigned char>& p_s_sig);
  /*!
   * \fn int sign_verif(const std::vector<unsigned char>& p_data, const std::vector<unsigned char>& p_signature);
   * \brief Verifiy an ECDSA signature
   * \param[in] p_data The signed data
   * \param[in] p_signature The signature part, based on r_sig part and s_sig part
   * \return 0 on success, -1 otherwise
   */ 
  int sign_verif(const std::vector<unsigned char>& p_data, const std::vector<unsigned char>& p_signature);
  
  /*!
   * \fn int generate_and_derive_ephemeral_key(const encryption_algotithm p_enc_algorithm, const std::vector<unsigned char>& p_recipients_public_key_x, const std::vector<unsigned char>& p_recipients_public_key_y);
   * \brief Generate a shared secret key and derive it using KDF2 algorithm.
   *        This method shall be used by the sender. Fresh keys will be genrated for each cyphering operation
   * \param[in] p_enc_algorithm The encryption algorithm to be used 
   * \param[in] p_recipients_public_key_x The recipient public key X-coordinate
   * \param[in] p_recipients_public_key_x The recipient public key Y-coordinate
   * \remark To get the secret key, uses \see secret_key method
   * \remark To get the generated symmetric encryption key, uses \see symmetric_encryption_key method
   * \remark To get the generated nonce vector, uses \see nonce method
   * \see encrypt methog to encrypt a message based of the generated symetric encryption key
   * \return 0 on success, -1 otherwise
   */
  int generate_and_derive_ephemeral_key(const encryption_algotithm p_enc_algorithm, const std::vector<unsigned char>& p_recipients_public_key_x, const std::vector<unsigned char>& p_recipients_public_key_y);

    /*!
   * \fn int generate_and_derive_ephemeral_key(const encryption_algotithm p_enc_algorithm, const std::vector<unsigned char>& p_private_enc_key, const std::vector<unsigned char>& p_ephemeral_public_key_x, const std::vector<unsigned char>& p_ephemeral_public_key_y);
   * \brief Generate a shared secret key and derive it using KDF2 algorithm.
   * \param[in] p_enc_algorithm The encryption algorithm to be used 
   * \param[in] p_private_enc_key The private encryption key associated to the public encryption key
   * \param[in] p_ephemeral_public_key_x The recipient public key X-coordinate
   * \param[in] p_ephemeral_public_key_x The recipient public key Y-coordinate
   * \remark To get the secret key, uses \see secret_key method
   * \remark To get the decrypteded symmetric encryption key, uses \see symmetric_encryption_key method
   * \return 0 on success, -1 otherwise
   */
  int generate_and_derive_ephemeral_key(const encryption_algotithm p_enc_algorithm, const std::vector<unsigned char>& p_private_enc_key, const std::vector<unsigned char>& p_ephemeral_public_key_x, const std::vector<unsigned char>& p_ephemeral_public_key_y, const std::vector<unsigned char>& p_enc_sym_key, const std::vector<unsigned char>& p_expected_nonce, const std::vector<unsigned char>& p_authentication_vector);

  /*!
   * \fn int encrypt(const encryption_algotithm p_enc_algorithm, const std::vector<unsigned char>& p_message, std::vector<unsigned char>& p_enc_message);
   * \brief Encryption using the specified algorithm, the encryption parameters are generated automatically.
   * \param[in] p_enc_algorithm The algorithm to use for the encryption
   * \param[in] p_message The message to be encrypted
   * \param[out] p_enc_message The encrypted message
   * \remark To get the generated symmetric encryption key, uses \see symmetric_encryption_key method
   * \remark To get the generated nonce vector, uses \see nonce method
   * \remark To get the generated tag, uses \see tag method
   * \return 0 on success, -1 otherwise
   */ 
  int encrypt(const encryption_algotithm p_enc_algorithm, const std::vector<unsigned char>& p_message, std::vector<unsigned char>& p_enc_message);
  /*!
   * \fn int encrypt(const encryption_algotithm p_enc_algorithm, const std::vector<unsigned char>& p_symmetric_key, const std::vector<unsigned char>& p_nonce, const std::vector<unsigned char>& p_message, std::vector<unsigned char>& p_enc_message);
   * \brief Encryption using the specified algorithm. The encryption parameters are provided by the caller (e.g. ECIES encryption).
   * \param[in] p_enc_algorithm The algorithm to use for the encryption
   * \param[in] p_symmetric_key
   * \param[in] p_nonce
   * \param[in] p_message The message to be encrypted
   * \param[out] p_enc_message The encrypted message
   * \remark To get the generated symmetric encryption key, uses \see symmetric_encryption_key method
   * \remark To get the generated nonce vector, uses \see nonce method
   * \remark To get the generated tag, uses \see tag method
   * \return 0 on success, -1 otherwise
   */
  int encrypt(const encryption_algotithm p_enc_algorithm, const std::vector<unsigned char>& p_symmetric_key, const std::vector<unsigned char>& p_nonce, const std::vector<unsigned char>& p_message, std::vector<unsigned char>& p_enc_message);
  /*!
   * \fn int decrypt(const encryption_algotithm p_enc_algorithm, const std::vector<unsigned char>& p_key, const std::vector<unsigned char>& p_nonce, const std::vector<unsigned char>& p_tag, const std::vector<unsigned char>& p_enc_message, std::vector<unsigned char>& p_message);
   * \brief Decryption using the specified parameters.
   * \param[in] p_enc_algorithm The algorithm to use for the decryption
   * \param[in] p_nonce The algorithm to use for the encryption
   * \param[in] p_tag The algorithm to use for the encryption
   * \param[in] p_enc_message The message to be decrypted
   * \param[out] p_message The decrypted message
   * \remark To get the generated symmetric encryption key, uses \see symmetric_encryption_key method
   * \remark To get the generated nonce vector, uses \see nonce method
   * \remark To get the generated tag, uses \see tag method
   * \return 0 on success, -1 otherwise
   */
  int decrypt(const encryption_algotithm p_enc_algorithm, const std::vector<unsigned char>& p_symmetric_key, const std::vector<unsigned char>& p_nonce, const std::vector<unsigned char>& p_tag, const std::vector<unsigned char>& p_enc_message, std::vector<unsigned char>& p_message);
  /*!
   * \fn int decrypt(const encryption_algotithm p_enc_algorithm, const std::vector<unsigned char>& p_key, const std::vector<unsigned char>& p_nonce, const std::vector<unsigned char>& p_tag, const std::vector<unsigned char>& p_enc_message, std::vector<unsigned char>& p_message);
   * \brief Decryption using default parameters.
   * \param[in] p_tag The algorithm to use for the encryption
   * \param[in] p_enc_message The message to be decrypted
   * \param[out] p_message The decrypted message
   * \remark To get the generated symmetric encryption key, uses \see symmetric_encryption_key method
   * \remark To get the generated nonce vector, uses \see nonce method
   * \remark To get the generated tag, uses \see tag method
   * \return 0 on success, -1 otherwise
   */
  int decrypt(const std::vector<unsigned char>& p_tag, const std::vector<unsigned char>& p_enc_message, std::vector<unsigned char>& p_message);
  
  inline const std::vector<unsigned char>& private_key() const { return _pri_key; };
  inline const std::vector<unsigned char>& public_key_x() const { return _pub_key_x; };
  inline const std::vector<unsigned char>& public_key_y() const { return _pub_key_y; };
  inline const std::vector<unsigned char>& public_key_compressed() const { return _pub_key_compressed; };
  inline const ecc_compressed_mode& public_key_compressed_mode() const { return _pub_key_compressed_mode; };
  inline const std::vector<unsigned char>& secret_key() const { return _secret_key; };
  inline const std::vector<unsigned char>& encryption_key_x() const { return _enc_key_x; };
  inline const std::vector<unsigned char>& encryption_key_y() const { return _enc_key_y; };
  inline const std::vector<unsigned char>& encrypted_symmetric_key() const { return _enc_sym_key; };
  inline const std::vector<unsigned char>& symmetric_encryption_key() const { return _sym_key; };
  inline const std::vector<unsigned char>& nonce() const { return _nonce; };
  inline const std::vector<unsigned char>& tag() const { return _tag; };
  
private: //! \privatesection
  /*!
   * \fn const int init();
   * \brief Called by the constructors to set up encryption environment
   * \return 0 on success, -1 otherwise
   */
  const int init();
  /*!
   * \fn int bin_to_ec_point(const std::vector<unsigned char>& p_public_key_x, const std::vector<unsigned char>& p_public_key_y, EC_POINT** p_ec_point);
   * \brief Convert a big number object into a public key
   * \return 0 on success, -1 otherwise
   */
  int bin_to_ec_point(const std::vector<unsigned char>& p_public_key_x, const std::vector<unsigned char>& p_public_key_y, EC_POINT** p_ec_point);
  /*!
   * \fn int public_key_to_bin(std::vector<unsigned char>& p_bin_key);
   * \brief Convert a public key into a big number object
   * \return 0 on success, -1 otherwise
   */
  int public_key_to_bin(std::vector<unsigned char>& p_bin_key);

  /*int multiply_point_with_bn(const EC_POINT &a, const BIGNUM* b, EC_POINT **P);
  // R: XY-coordinate compressed, S: Share Secret
  int derive_s_from_private_key(BIGNUM *S, BIGNUM *R);
  // R: XY-coordinate compressed, S: Share Secret
  int derive_s_from_public_key(BIGNUM *S, BIGNUM *R);*/

}; // End of class security_ecc
