/**
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
 * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

#include "noise.h"
#include <string.h>

#include "aes/aesgcm.h"
#include "ed25519-donna/ed25519.h"
#include "hmac.h"
#include "memzero.h"
#include "rand.h"
#include "sha2.h"

static uint8_t protocol_name[SHA256_DIGEST_LENGTH] = {
    'N', 'o', 'i', 's', 'e', '_', 'K', 'K',  '1',  '_', '2',
    '5', '5', '1', '9', '_', 'A', 'E', 'S',  'G',  'C', 'M',
    '_', 'S', 'H', 'A', '2', '5', '6', 0x00, 0x00, 0x00};

static bool encrypt(const uint8_t key[NOISE_KEY_SIZE],
                    const uint8_t nonce[NOISE_NONCE_SIZE],
                    const uint8_t *associated_data,
                    size_t associated_data_length, const uint8_t *plaintext,
                    size_t plaintext_length, uint8_t *ciphertext) {
  // ciphertext = AES-GCM-Encrypt(key, nonce, associated_data, plaintext)
  gcm_ctx ctx = {0};
  if (gcm_init_and_key(key, NOISE_KEY_SIZE, &ctx) != RETURN_GOOD) {
    return false;
  }

  memcpy(ciphertext, plaintext, plaintext_length);

  if (gcm_encrypt_message(nonce, NOISE_NONCE_SIZE, associated_data,
                          associated_data_length, ciphertext, plaintext_length,
                          ciphertext + plaintext_length, NOISE_TAG_SIZE,
                          &ctx) != RETURN_GOOD) {
    memzero(&ctx, sizeof(ctx));
    memzero(ciphertext, plaintext_length);
    return false;
  }
  memzero(&ctx, sizeof(ctx));

  return true;
}

static bool decrypt(const uint8_t key[NOISE_KEY_SIZE],
                    const uint8_t nonce[NOISE_NONCE_SIZE],
                    const uint8_t *associated_data,
                    size_t associated_data_length, const uint8_t *ciphertext,
                    size_t ciphertext_length, uint8_t *plaintext) {
  // plaintext = AES-GCM-Decrypt(key, nonce, associated_data, ciphertext)
  if (ciphertext_length < NOISE_TAG_SIZE) {
    return false;
  }
  const size_t plaintext_length = ciphertext_length - NOISE_TAG_SIZE;

  gcm_ctx ctx = {0};
  if (gcm_init_and_key(key, NOISE_KEY_SIZE, &ctx) != RETURN_GOOD) {
    return false;
  }

  memcpy(plaintext, ciphertext, plaintext_length);

  if (gcm_decrypt_message(nonce, NOISE_NONCE_SIZE, associated_data,
                          associated_data_length, plaintext, plaintext_length,
                          ciphertext + plaintext_length, NOISE_TAG_SIZE,
                          &ctx) != RETURN_GOOD) {
    memzero(&ctx, sizeof(ctx));
    memzero(plaintext, plaintext_length);
    return false;
  }
  memzero(&ctx, sizeof(ctx));

  return true;
}

static void mix_hash(uint8_t hash[SHA256_DIGEST_LENGTH], const uint8_t *data,
                     size_t data_length) {
  // hash = SHA256(hash || data)
  SHA256_CTX sha256_ctx;
  sha256_Init(&sha256_ctx);
  sha256_Update(&sha256_ctx, hash, SHA256_DIGEST_LENGTH);
  sha256_Update(&sha256_ctx, data, data_length);
  sha256_Final(&sha256_ctx, hash);
}

static void hkdf(const uint8_t *salt, size_t salt_length, const uint8_t *key,
                 size_t key_length, uint8_t output1[SHA256_DIGEST_LENGTH],
                 uint8_t output2[SHA256_DIGEST_LENGTH]) {
  // output1 || output2 = HKDF(salt, key, output_length=2*SHA256_DIGEST_LENGTH)
  uint8_t prk[SHA256_DIGEST_LENGTH] = {0};
  hmac_sha256(salt, salt_length, key, key_length, prk);

  uint8_t message[SHA256_DIGEST_LENGTH + 1] = {0};
  message[0] = 1;
  hmac_sha256(prk, sizeof(prk), message, 1, output1);

  if (output2) {
    memcpy(message, output1, SHA256_DIGEST_LENGTH);
    message[SHA256_DIGEST_LENGTH] = 2;
    hmac_sha256(prk, sizeof(prk), message, SHA256_DIGEST_LENGTH + 1, output2);
  }

  memzero(message, sizeof(message));
  memzero(prk, sizeof(prk));
}

static void mix_key(uint8_t chaining_key[SHA256_DIGEST_LENGTH],
                    curve25519_key input_key,
                    uint8_t output_key[NOISE_KEY_SIZE]) {
  // chaining_key || output_key =
  //   HKDF(salt=chaining_key, key=input_key, output_length=2*NOISE_KEY_SIZE)
  hkdf(chaining_key, SHA256_DIGEST_LENGTH, input_key, sizeof(curve25519_key),
       chaining_key, output_key);
  _Static_assert(NOISE_KEY_SIZE == SHA256_DIGEST_LENGTH,
                 "output_key must be truncated to NOISE_KEY_SIZE");
}

void split(uint8_t chaining_key[SHA256_DIGEST_LENGTH],
           uint8_t output1[NOISE_KEY_SIZE], uint8_t output2[NOISE_KEY_SIZE]) {
  // output1 || output2 =
  //   HKDF(salt=chaining_key, key=b"", output_length=2*NOISE_KEY_SIZE)
  hkdf(chaining_key, SHA256_DIGEST_LENGTH, NULL, 0, output1, output2);
  _Static_assert(NOISE_KEY_SIZE == SHA256_DIGEST_LENGTH,
                 "output1 and output2 must be truncated to NOISE_KEY_SIZE");
}

static bool increase_nonce(uint8_t nonce[NOISE_NONCE_SIZE]) {
  // The first 4 bytes of the nonce are zeros
  // The last 8 bytes of the nonce are a big-endian encoded counter
  for (int i = NOISE_NONCE_SIZE - 1; i >= 4; i--) {
    nonce[i]++;
    if (nonce[i] != 0) {
      return true;
    }
  }

  // Nonce overflow
  return false;
}

bool noise_create_handshake_request(noise_context_t *ctx,
                                    noise_request_t *request) {
  memzero(ctx, sizeof(*ctx));
  ctx->initialized = false;

  random_buffer(ctx->initiator_ephemeral_private_key,
                sizeof(ctx->initiator_ephemeral_private_key));
  curve25519_scalarmult_basepoint(request->initiator_ephemeral_public_key,
                                  ctx->initiator_ephemeral_private_key);
  return true;
}

bool noise_handle_handshake_request(noise_context_t *ctx,
                                    const curve25519_key initiator_public_key,
                                    const curve25519_key responder_private_key,
                                    const noise_request_t *request,
                                    noise_response_t *response) {
  memzero(ctx, sizeof(*ctx));

  curve25519_key responder_public_key = {0};
  curve25519_scalarmult_basepoint(responder_public_key, responder_private_key);

  curve25519_key responder_ephemeral_private_key = {0};
  random_buffer(responder_ephemeral_private_key,
                sizeof(responder_ephemeral_private_key));
  curve25519_key responder_ephemeral_public_key = {0};
  curve25519_scalarmult_basepoint(responder_ephemeral_public_key,
                                  responder_ephemeral_private_key);

  uint8_t handshake_hash[SHA256_DIGEST_LENGTH] = {0};
  memcpy(handshake_hash, protocol_name, sizeof(protocol_name));
  mix_hash(handshake_hash, NULL, 0);
  mix_hash(handshake_hash, initiator_public_key, sizeof(curve25519_key));
  mix_hash(handshake_hash, responder_public_key, sizeof(curve25519_key));
  mix_hash(handshake_hash, request->initiator_ephemeral_public_key,
           sizeof(curve25519_key));
  mix_hash(handshake_hash, NULL, 0);  // Empty message
  mix_hash(handshake_hash, responder_ephemeral_public_key,
           sizeof(curve25519_key));

  curve25519_key shared_secret = {0};
  uint8_t chaining_key[SHA256_DIGEST_LENGTH] = {0};
  uint8_t kauth[NOISE_KEY_SIZE] = {0};
  memcpy(chaining_key, protocol_name, sizeof(protocol_name));
  curve25519_scalarmult(shared_secret, responder_ephemeral_private_key,
                        request->initiator_ephemeral_public_key);
  mix_key(chaining_key, shared_secret, NULL);
  curve25519_scalarmult(shared_secret, responder_ephemeral_private_key,
                        initiator_public_key);
  memzero(responder_ephemeral_private_key,
          sizeof(responder_ephemeral_private_key));
  mix_key(chaining_key, shared_secret, NULL);
  curve25519_scalarmult(shared_secret, responder_private_key,
                        request->initiator_ephemeral_public_key);
  mix_key(chaining_key, shared_secret, kauth);
  memzero(shared_secret, sizeof(shared_secret));
  split(chaining_key, ctx->decryption_key, ctx->encryption_key);
  memzero(chaining_key, sizeof(chaining_key));

  memcpy(response, responder_ephemeral_public_key, sizeof(curve25519_key));

  uint8_t zero_nonce[NOISE_NONCE_SIZE] = {0};
  encrypt(kauth, zero_nonce, handshake_hash, sizeof(handshake_hash), NULL, 0,
          response->tag);
  memzero(kauth, sizeof(kauth));

  // This is unnecessary, as the handshake hash is no longer used.
  // mix_hash(handshake_hash, response->tag, sizeof(response->tag));

  memset(ctx->encryption_nonce, 0, NOISE_NONCE_SIZE);
  memset(ctx->decryption_nonce, 0, NOISE_NONCE_SIZE);

  ctx->initialized = true;

  return true;
}

bool noise_handle_handshake_response(noise_context_t *ctx,
                                     const curve25519_key initiator_private_key,
                                     const curve25519_key responder_public_key,
                                     const noise_response_t *response) {
  curve25519_key initiator_public_key = {0};
  curve25519_scalarmult_basepoint(initiator_public_key, initiator_private_key);

  curve25519_key initiator_ephemeral_public_key = {0};
  curve25519_scalarmult_basepoint(initiator_ephemeral_public_key,
                                  ctx->initiator_ephemeral_private_key);

  uint8_t handshake_hash[SHA256_DIGEST_LENGTH] = {0};
  memcpy(handshake_hash, protocol_name, sizeof(protocol_name));
  mix_hash(handshake_hash, NULL, 0);
  mix_hash(handshake_hash, initiator_public_key, sizeof(curve25519_key));
  mix_hash(handshake_hash, responder_public_key, sizeof(curve25519_key));
  mix_hash(handshake_hash, initiator_ephemeral_public_key,
           sizeof(curve25519_key));
  mix_hash(handshake_hash, NULL, 0);  // Empty message
  mix_hash(handshake_hash, response->responder_ephemeral_public_key,
           sizeof(curve25519_key));

  curve25519_key shared_secret = {0};
  uint8_t chaining_key[SHA256_DIGEST_LENGTH] = {0};
  uint8_t kauth[NOISE_KEY_SIZE] = {0};
  memcpy(chaining_key, protocol_name, sizeof(protocol_name));
  curve25519_scalarmult(shared_secret, ctx->initiator_ephemeral_private_key,
                        response->responder_ephemeral_public_key);
  mix_key(chaining_key, shared_secret, NULL);
  curve25519_scalarmult(shared_secret, initiator_private_key,
                        response->responder_ephemeral_public_key);
  mix_key(chaining_key, shared_secret, NULL);
  curve25519_scalarmult(shared_secret, ctx->initiator_ephemeral_private_key,
                        responder_public_key);
  memzero(ctx->initiator_ephemeral_private_key,
          sizeof(ctx->initiator_ephemeral_private_key));
  mix_key(chaining_key, shared_secret, kauth);
  memzero(shared_secret, sizeof(shared_secret));
  split(chaining_key, ctx->encryption_key, ctx->decryption_key);
  memzero(chaining_key, sizeof(chaining_key));

  uint8_t zero_nonce[NOISE_NONCE_SIZE] = {0};
  if (!decrypt(kauth, zero_nonce, handshake_hash, sizeof(handshake_hash),
               response->tag, NOISE_TAG_SIZE, NULL)) {
    // Wrong tag
    memzero(kauth, sizeof(kauth));
    return false;
  }
  memzero(kauth, sizeof(kauth));

  // This is unnecessary, as the handshake hash is no longer used.
  // mix_hash(handshake_hash, response->tag, sizeof(response->tag));

  memset(ctx->encryption_nonce, 0, NOISE_NONCE_SIZE);
  memset(ctx->decryption_nonce, 0, NOISE_NONCE_SIZE);

  ctx->initialized = true;

  return true;
}

bool noise_send_message(noise_context_t *ctx, const uint8_t *associated_data,
                        size_t associated_data_length, const uint8_t *plaintext,
                        size_t plaintext_length, uint8_t *ciphertext) {
  if (!ctx->initialized) {
    return false;
  }
  if (!encrypt(ctx->encryption_key, ctx->encryption_nonce, associated_data,
               associated_data_length, plaintext, plaintext_length,
               ciphertext)) {
    return false;
  }
  if (!increase_nonce(ctx->encryption_nonce)) {
    // Nonce overflow
    memzero(ctx, sizeof(*ctx));
    ctx->initialized = false;
    return false;
  }

  return true;
}

bool noise_receive_message(noise_context_t *ctx, const uint8_t *associated_data,
                           size_t associated_data_length,
                           const uint8_t *ciphertext, size_t ciphertext_length,
                           uint8_t *plaintext) {
  if (!ctx->initialized) {
    return false;
  }
  if (!decrypt(ctx->decryption_key, ctx->decryption_nonce, associated_data,
               associated_data_length, ciphertext, ciphertext_length,
               plaintext)) {
    // Wrong tag
    return false;
  }
  if (!increase_nonce(ctx->decryption_nonce)) {
    // Nonce overflow
    memzero(ctx, sizeof(*ctx));
    ctx->initialized = false;
    return false;
  }
  return true;
}

bool noise_handle_handshake_response_multiple_keys(
    noise_context_t *ctx, const curve25519_key initiator_private_key,
    const curve25519_key *responder_public_keys,
    size_t responder_public_keys_count, const noise_response_t *response) {
  curve25519_key ephemeral_key_backup = {0};
  memcpy(ephemeral_key_backup, ctx->initiator_ephemeral_private_key,
         sizeof(ephemeral_key_backup));
  for (size_t i = 0; i < responder_public_keys_count; i++) {
    memcpy(ctx->initiator_ephemeral_private_key, ephemeral_key_backup,
           sizeof(ephemeral_key_backup));
    if (noise_handle_handshake_response(ctx, initiator_private_key,
                                        responder_public_keys[i], response)) {
      memzero(ephemeral_key_backup, sizeof(ephemeral_key_backup));
      return true;
    }
  }
  return false;
}
