/*
  This file is part of Anastasis
  Copyright (C) 2020 Anastasis SARL

  Anastasis is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
  Foundation; either version 3, or (at your option) any later version.

  Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY
  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.

  You should have received a copy of the GNU General Public License along with
  Anastasis; see the file COPYING.GPL.  If not, see <http://www.gnu.org/licenses/>
*/
/**
 * @file util/anastasis_crypto.c
 * @brief anastasis crypto api
 * @author Christian Grothoff
 * @author Dominik Meister
 * @author Dennis Neufeld
 */
#include "platform.h"
#include "anastasis_crypto_lib.h"
#include <gcrypt.h>
#include <taler/taler_json_lib.h>
#include <gnunet/gnunet_util_lib.h>
#include <string.h>


void
ANASTASIS_hash_answer (uint64_t code,
                       struct GNUNET_HashCode *hashed_code)
{
  char cbuf[40];

  GNUNET_snprintf (cbuf,
                   sizeof (cbuf),
                   "%llu",
                   (unsigned long long) code);
  GNUNET_CRYPTO_hash (cbuf,
                      strlen (cbuf),
                      hashed_code);
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Hashed answer to %s\n",
              GNUNET_h2s (hashed_code));
}


void
ANASTASIS_CRYPTO_secure_answer_hash (
  const char *answer,
  const struct ANASTASIS_CRYPTO_TruthUUIDP *uuid,
  const struct ANASTASIS_CRYPTO_QuestionSaltP *salt,
  struct GNUNET_HashCode *result)
{
  struct GNUNET_HashCode pow;

  GNUNET_CRYPTO_pow_hash (&salt->pow_salt,
                          answer,
                          strlen (answer),
                          &pow);
  GNUNET_assert (GNUNET_YES ==
                 GNUNET_CRYPTO_hkdf_gnunet (
                   result,
                   sizeof (*result),
                   /* salt / XTS */
                   uuid,
                   sizeof (*uuid),
                   /* skm */
                   &pow,
                   sizeof (pow),
                   GNUNET_CRYPTO_kdf_arg_string (
                     "anastasis-secure-question-hashing")));
}


/**
 * Compute @a key.
 *
 * @param key_material key for calculation
 * @param key_m_len length of key
 * @param nonce nonce for calculation
 * @param salt salt value for calculation
 * @param[out] key where to write the en-/description key
 */
static void
derive_key (const void *key_material,
            size_t key_m_len,
            const struct ANASTASIS_CRYPTO_NonceP *nonce,
            const char *salt,
            struct ANASTASIS_CRYPTO_SymKeyP *key)
{
  GNUNET_assert (GNUNET_YES ==
                 GNUNET_CRYPTO_hkdf_gnunet (
                   key,
                   sizeof (*key),
                   /* salt / XTS */
                   nonce,
                   sizeof (*nonce),
                   /* ikm */
                   key_material,
                   key_m_len,
                   /* info chunks */
                   /* The "salt" passed here is actually not something random,
                      but a protocol-specific identifier string.  Thus
                      we pass it as a context info to the HKDF */
                   GNUNET_CRYPTO_kdf_arg_string (salt)));
}


/**
 * Encryption of data like recovery document etc.
 *
 * @param nonce value to use for the nonce
 * @param key key which is used to derive a key/iv pair from
 * @param key_len length of key
 * @param data data to encrypt
 * @param data_size size of the data
 * @param salt salt value which is used for key derivation
 * @param[out] res ciphertext output
 * @param[out] res_size size of the ciphertext
 */
static void
anastasis_encrypt (const struct ANASTASIS_CRYPTO_NonceP *nonce,
                   const void *key,
                   size_t key_len,
                   const void *data,
                   size_t data_size,
                   const char *salt,
                   void **res,
                   size_t *res_size)
{
  size_t ciphertext_size;
  struct ANASTASIS_CRYPTO_SymKeyP skey;

  derive_key (key,
              key_len,
              nonce,
              salt,
              &skey);
  ciphertext_size = crypto_secretbox_NONCEBYTES
                    + crypto_secretbox_MACBYTES + data_size;
  *res_size = ciphertext_size;
  *res = GNUNET_malloc (ciphertext_size);
  memcpy (*res, nonce, crypto_secretbox_NONCEBYTES);
  GNUNET_assert (0 ==
                 crypto_secretbox_easy (*res + crypto_secretbox_NONCEBYTES,
                                        data,
                                        data_size,
                                        (void *) nonce,
                                        (void *) &skey));
}


/**
 * Decryption of data like encrypted recovery document etc.
 *
 * @param key key which is used to derive a key/iv pair from
 * @param key_len length of key
 * @param data data to decrypt
 * @param data_size size of the data
 * @param salt salt value which is used for key derivation
 * @param[out] res plaintext output
 * @param[out] res_size size of the plaintext
 * @return #GNUNET_OK on success
 */
static enum GNUNET_GenericReturnValue
anastasis_decrypt (const void *key,
                   size_t key_len,
                   const void *data,
                   size_t data_size,
                   const char *salt,
                   void **res,
                   size_t *res_size)
{
  const struct ANASTASIS_CRYPTO_NonceP *nonce;
  struct ANASTASIS_CRYPTO_SymKeyP skey;
  size_t plaintext_size;

  if (data_size < crypto_secretbox_NONCEBYTES + crypto_secretbox_MACBYTES)
  {
    /* short ciphertext is what a hostile provider sends, not a local bug */
    GNUNET_break_op (0);
    *res = NULL;
    *res_size = 0;
    return GNUNET_SYSERR;
  }
  nonce = data;
  derive_key (key,
              key_len,
              nonce,
              salt,
              &skey);
  plaintext_size = data_size - (crypto_secretbox_NONCEBYTES
                                + crypto_secretbox_MACBYTES);
  *res = GNUNET_malloc (plaintext_size);
  *res_size = plaintext_size;
  if (0 != crypto_secretbox_open_easy (*res,
                                       data + crypto_secretbox_NONCEBYTES,
                                       data_size - crypto_secretbox_NONCEBYTES,
                                       (void *) nonce,
                                       (void *) &skey))
  {
    /* likewise: a wrong answer or a hostile provider, not a local bug */
    GNUNET_break_op (0);
    GNUNET_free (*res);
    *res = NULL;
    *res_size = 0;
    return GNUNET_SYSERR;
  }
  return GNUNET_OK;
}


void
ANASTASIS_CRYPTO_user_identifier_derive (
  const json_t *id_data,
  const struct ANASTASIS_CRYPTO_ProviderSaltP *provider_salt,
  struct ANASTASIS_CRYPTO_UserIdentifierP *id)
{
  char *json_enc;
  struct GNUNET_HashCode hash;

  json_enc = json_dumps (id_data,
                         JSON_COMPACT | JSON_SORT_KEYS);
  GNUNET_assert (NULL != json_enc);
  GNUNET_CRYPTO_pow_hash (&provider_salt->salt,
                          json_enc,
                          strlen (json_enc),
                          &hash);
  id->hash = hash;
  free (json_enc);
}


void
ANASTASIS_CRYPTO_account_private_key_derive (
  const struct ANASTASIS_CRYPTO_UserIdentifierP *id,
  struct ANASTASIS_CRYPTO_AccountPrivateKeyP *priv_key)
{
  /* priv_key = ver_secret */
  GNUNET_assert (GNUNET_YES ==
                 GNUNET_CRYPTO_hkdf_gnunet (
                   &priv_key->priv,
                   sizeof (priv_key->priv),
                   /* salt / XTS */
                   NULL,
                   0,
                   /* ikm */
                   id,
                   sizeof (struct ANASTASIS_CRYPTO_UserIdentifierP),
                   /* context chunks */
                   GNUNET_CRYPTO_kdf_arg_string ("ver")));
}


void
ANASTASIS_CRYPTO_account_public_key_derive (
  const struct ANASTASIS_CRYPTO_UserIdentifierP *id,
  struct ANASTASIS_CRYPTO_AccountPublicKeyP *pub_key)
{
  struct ANASTASIS_CRYPTO_AccountPrivateKeyP priv;

  ANASTASIS_CRYPTO_account_private_key_derive (id,
                                               &priv);
  GNUNET_CRYPTO_eddsa_key_get_public (&priv.priv,
                                      &pub_key->pub);
}


void
ANASTASIS_CRYPTO_recovery_document_encrypt (
  const struct ANASTASIS_CRYPTO_UserIdentifierP *id,
  const void *rec_doc,
  size_t rd_size,
  void **enc_rec_doc,
  size_t *erd_size)
{
  const char *salt = "erd";
  struct ANASTASIS_CRYPTO_NonceP nonce;

  GNUNET_CRYPTO_random_block (&nonce,
                              sizeof (nonce));
  anastasis_encrypt (&nonce,
                     id,
                     sizeof (struct ANASTASIS_CRYPTO_UserIdentifierP),
                     rec_doc,
                     rd_size,
                     salt,
                     enc_rec_doc,
                     erd_size);
}


enum GNUNET_GenericReturnValue
ANASTASIS_CRYPTO_recovery_document_decrypt (
  const struct ANASTASIS_CRYPTO_UserIdentifierP *id,
  const void *enc_rec_doc,
  size_t erd_size,
  void **rec_doc,
  size_t *rd_size)
{
  const char *salt = "erd";

  return anastasis_decrypt (id,
                            sizeof (struct ANASTASIS_CRYPTO_UserIdentifierP),
                            enc_rec_doc,
                            erd_size,
                            salt,
                            rec_doc,
                            rd_size);
}


void
ANASTASIS_CRYPTO_keyshare_encrypt (
  const struct ANASTASIS_CRYPTO_KeyShareP *key_share,
  const struct ANASTASIS_CRYPTO_UserIdentifierP *id,
  const char *xsalt,
  struct ANASTASIS_CRYPTO_EncryptedKeyShareP *enc_key_share)
{
  const char *salt = "eks";
  size_t eks_size = 0;
  void *eks = NULL;
  struct ANASTASIS_CRYPTO_NonceP nonce;

  GNUNET_CRYPTO_random_block (&nonce,
                              sizeof (nonce));
  anastasis_encrypt (&nonce,
                     id,
                     sizeof (*id),
                     key_share,
                     sizeof (*key_share),
                     (NULL == xsalt) ? salt : xsalt,
                     &eks,
                     &eks_size);
  GNUNET_assert (eks_size ==
                 sizeof (struct ANASTASIS_CRYPTO_EncryptedKeyShareP));
  memcpy (enc_key_share,
          eks,
          sizeof (struct ANASTASIS_CRYPTO_EncryptedKeyShareP));
  GNUNET_free (eks);
}


enum GNUNET_GenericReturnValue
ANASTASIS_CRYPTO_keyshare_decrypt (
  const struct ANASTASIS_CRYPTO_EncryptedKeyShareP *enc_key_share,
  const struct ANASTASIS_CRYPTO_UserIdentifierP *id,
  const char *xsalt,
  struct ANASTASIS_CRYPTO_KeyShareP *key_share)
{
  const char *salt = "eks";
  size_t ks_size = 0;
  void *ks = NULL;

  if (GNUNET_OK !=
      anastasis_decrypt (id,
                         sizeof (*id),
                         enc_key_share,
                         sizeof (*enc_key_share),
                         (NULL == xsalt) ? salt : xsalt,
                         &ks,
                         &ks_size))
    return GNUNET_SYSERR;
  if (ks_size !=
      sizeof (struct ANASTASIS_CRYPTO_KeyShareP))
  {
    /* the ciphertext came from an untrusted provider, so a wrong
       plaintext size must not be an assertion */
    GNUNET_break_op (0);
    GNUNET_free (ks);
    return GNUNET_SYSERR;
  }
  memcpy (key_share,
          ks,
          sizeof (struct ANASTASIS_CRYPTO_KeyShareP));
  GNUNET_free (ks);
  return GNUNET_OK;
}


void
ANASTASIS_CRYPTO_truth_encrypt (
  const struct ANASTASIS_CRYPTO_NonceP *nonce,
  const struct ANASTASIS_CRYPTO_TruthKeyP *truth_enc_key,
  const void *truth,
  size_t truth_size,
  void **enc_truth,
  size_t *ect_size)
{
  const char *salt = "ect";

  anastasis_encrypt (nonce,
                     truth_enc_key,
                     sizeof (struct ANASTASIS_CRYPTO_TruthKeyP),
                     truth,
                     truth_size,
                     salt,
                     enc_truth,
                     ect_size);
}


enum GNUNET_GenericReturnValue
ANASTASIS_CRYPTO_truth_decrypt (
  const struct ANASTASIS_CRYPTO_TruthKeyP *truth_enc_key,
  const void *enc_truth,
  size_t ect_size,
  void **truth,
  size_t *truth_size)
{
  const char *salt = "ect";

  return anastasis_decrypt (truth_enc_key,
                            sizeof (struct ANASTASIS_CRYPTO_TruthKeyP),
                            enc_truth,
                            ect_size,
                            salt,
                            truth,
                            truth_size);
}


void
ANASTASIS_CRYPTO_keyshare_create (
  struct ANASTASIS_CRYPTO_KeyShareP *key_share)
{
  GNUNET_CRYPTO_random_block (key_share,
                              sizeof (struct ANASTASIS_CRYPTO_KeyShareP));
}


void
ANASTASIS_CRYPTO_policy_key_derive (
  const struct ANASTASIS_CRYPTO_KeyShareP *key_shares,
  unsigned int keyshare_length,
  const struct ANASTASIS_CRYPTO_MasterSaltP *salt,
  struct ANASTASIS_CRYPTO_PolicyKeyP *policy_key)
{
  GNUNET_assert (GNUNET_YES ==
                 GNUNET_CRYPTO_hkdf_gnunet (
                   policy_key,
                   sizeof (*policy_key),
                   /* salt / XTS */
                   salt,
                   sizeof (*salt),
                   /* ikm */
                   key_shares,
                   keyshare_length * sizeof (*key_shares),
                   /* info chunks */
                   GNUNET_CRYPTO_kdf_arg_string (
                     "anastasis-policy-key-derive")));
}


struct ANASTASIS_CoreSecretEncryptionResult *
ANASTASIS_CRYPTO_core_secret_encrypt (
  const struct ANASTASIS_CRYPTO_PolicyKeyP *policy_keys,
  unsigned int policy_keys_length,
  const void *core_secret,
  size_t core_secret_size)
{
  struct GNUNET_HashCode master_key;
  struct ANASTASIS_CoreSecretEncryptionResult *cser;
  struct ANASTASIS_CRYPTO_NonceP nonce;

  cser = GNUNET_new (struct ANASTASIS_CoreSecretEncryptionResult);

  GNUNET_CRYPTO_random_block (&master_key,
                              sizeof (struct GNUNET_HashCode));
  GNUNET_CRYPTO_random_block (&nonce,
                              sizeof (struct ANASTASIS_CRYPTO_NonceP));

  anastasis_encrypt (&nonce,
                     &master_key,
                     sizeof (struct GNUNET_HashCode),
                     core_secret,
                     core_secret_size,
                     "cse",
                     &cser->enc_core_secret,
                     &cser->enc_core_secret_size);

  /* Allocate result arrays with NULL-termination so we don't
     need to store the length to free */
  cser->enc_master_key_sizes = GNUNET_new_array (policy_keys_length + 1,
                                                 size_t);
  cser->enc_master_keys = GNUNET_new_array (policy_keys_length + 1,
                                            void *);

  for (unsigned int i = 0; i < policy_keys_length; i++)
  {
    struct ANASTASIS_CRYPTO_NonceP nonce_i;

    GNUNET_CRYPTO_random_block (&nonce_i,
                                sizeof (struct ANASTASIS_CRYPTO_NonceP));

    anastasis_encrypt (&nonce_i,
                       &policy_keys[i].key,
                       sizeof (struct GNUNET_HashCode),
                       &master_key,
                       sizeof (struct GNUNET_HashCode),
                       "emk",
                       &cser->enc_master_keys[i],
                       &cser->enc_master_key_sizes[i]);
  }
  return cser;
}


enum GNUNET_GenericReturnValue
ANASTASIS_CRYPTO_core_secret_recover (
  const void *encrypted_master_key,
  size_t encrypted_master_key_size,
  const struct ANASTASIS_CRYPTO_PolicyKeyP *policy_key,
  const void *encrypted_core_secret,
  size_t encrypted_core_secret_size,
  void **core_secret,
  size_t *core_secret_size)
{
  void *master_key;
  size_t master_key_size;

  *core_secret = NULL;
  *core_secret_size = 0;
  if (GNUNET_OK !=
      anastasis_decrypt (&policy_key->key,
                         sizeof (struct GNUNET_HashCode),
                         encrypted_master_key,
                         encrypted_master_key_size,
                         "emk",
                         &master_key,
                         &master_key_size))
  {
    /* the key shares came from providers we do not trust, so failing to
       reassemble the master key is an expected outcome, not a bug */
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }
  if (GNUNET_OK !=
      anastasis_decrypt (master_key,
                         master_key_size,
                         encrypted_core_secret,
                         encrypted_core_secret_size,
                         "cse",
                         core_secret,
                         core_secret_size))
  {
    GNUNET_break_op (0);
    GNUNET_free (master_key);
    return GNUNET_SYSERR;
  }
  GNUNET_free (master_key);
  /* Deliberately not logged: TALER_b2s() of the core secret is a ~40 bit
     fingerprint of it, which is enough to confirm a guess for anyone who can
     read the debug log. */
  return GNUNET_OK;
}


void
ANASTASIS_CRYPTO_destroy_encrypted_core_secret (
  struct ANASTASIS_CoreSecretEncryptionResult *cser)
{
  for (unsigned int i = 0; NULL != cser->enc_master_keys[i]; i++)
    GNUNET_free (cser->enc_master_keys[i]);
  GNUNET_free (cser->enc_master_keys);
  GNUNET_free (cser->enc_master_key_sizes);
  GNUNET_free (cser->enc_core_secret);
  GNUNET_free (cser);
}


const char *
ANASTASIS_CRYPTO_uuid2s (const struct ANASTASIS_CRYPTO_TruthUUIDP *uuid)
{
  static char uuids[7];
  char *tpk;

  tpk = GNUNET_STRINGS_data_to_string_alloc (uuid,
                                             sizeof (*uuid));
  memcpy (uuids,
          tpk,
          sizeof (uuids) - 1);
  GNUNET_free (tpk);
  return uuids;
}


void
ANASTASIS_CRYPTO_recovery_metadata_encrypt (
  const struct ANASTASIS_CRYPTO_UserIdentifierP *id,
  const void *meta_data,
  size_t meta_data_size,
  void **enc_meta_data,
  size_t *enc_meta_data_size)
{
  const char *salt = "rmd";
  struct ANASTASIS_CRYPTO_NonceP nonce;

  GNUNET_CRYPTO_random_block (&nonce,
                              sizeof (nonce));
  anastasis_encrypt (&nonce,
                     id,
                     sizeof (*id),
                     meta_data,
                     meta_data_size,
                     salt,
                     enc_meta_data,
                     enc_meta_data_size);
}


enum GNUNET_GenericReturnValue
ANASTASIS_CRYPTO_recovery_metadata_decrypt (
  const struct ANASTASIS_CRYPTO_UserIdentifierP *id,
  const void *enc_meta_data,
  size_t enc_meta_data_size,
  void **meta_data,
  size_t *meta_data_size)
{
  const char *salt = "rmd";

  return anastasis_decrypt (id,
                            sizeof (*id),
                            enc_meta_data,
                            enc_meta_data_size,
                            salt,
                            meta_data,
                            meta_data_size);
}


/* end of anastasis_crypto.c */
