/* Copyright (c) 2001 Matej Pfajfar.
 * Copyright (c) 2001-2004, Roger Dingledine.
 * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
 * Copyright (c) 2007-2021, The Tor Project, Inc. */
/* See LICENSE for licensing information */

/**
 * @file relay_crypto.h
 * @brief Header for relay_crypto.c
 **/

// For access to cpath pvt_crypto field.
#define CRYPT_PATH_PRIVATE

#include "core/or/or.h"
#include "core/or/circuitlist.h"
#include "core/or/crypt_path.h"
#include "app/config/config.h"
#include "lib/crypt_ops/crypto_cipher.h"
#include "lib/crypt_ops/crypto_util.h"
#include "core/crypto/relay_crypto.h"
#include "core/crypto/relay_crypto_tor1.h"
#include "core/or/sendme.h"

#include "core/or/or_circuit_st.h"
#include "core/or/cell_st.h"
#include "core/or/origin_circuit_st.h"

#define CGO_AES_BITS 128

/** Return the sendme tag within the <b>crypto</b> object,
 * along with its length.
 *
 * This is the digest from the most recent cell that we originated
 * or recognized, _in either direction_.
 * Calls to any encryption function on `crypto` may invalidate
 * this digest.
 */
const uint8_t *
relay_crypto_get_sendme_tag(relay_crypto_t *crypto,
                            size_t *len_out)
{
  tor_assert(crypto);
  switch (crypto->kind) {
    case RCK_TOR1:
      *len_out = SENDME_TAG_LEN_TOR1;
      return crypto->c.tor1.sendme_digest;
    case RCK_CGO:
      *len_out = SENDME_TAG_LEN_CGO;
      return crypto->c.cgo.last_tag;
  }
  tor_assert_unreached();
}

/** Return the length of SENDME tags generated by `crypto`. */
size_t
relay_crypto_sendme_tag_len(const relay_crypto_t *crypto)
{
  tor_assert(crypto);
  switch (crypto->kind) {
    case RCK_TOR1:
      return SENDME_TAG_LEN_TOR1;
    case RCK_CGO:
      return SENDME_TAG_LEN_CGO;
  }
  tor_assert_unreached();
}

/**
 * Handle a single layer of client-side backward encryption
 * with crypto of an arbitrary type.
 */
static inline bool
relay_crypt_client_backward(relay_crypto_t *crypto, cell_t *cell)
{
  switch (crypto->kind) {
    case RCK_TOR1:
      return tor1_crypt_client_backward(&crypto->c.tor1, cell);
    case RCK_CGO: {
      const uint8_t *tag = NULL;
      cgo_crypt_client_backward(crypto->c.cgo.back, cell, &tag);
      if (tag != NULL) {
        memcpy(crypto->c.cgo.last_tag, tag, SENDME_TAG_LEN_CGO);
        return true;
      } else {
        return false;
      }
    }
  }
  tor_assert_unreached();
}

/**
 * Handle a relay-side forward encryption
 * with crypto of an arbitrary type.
 */
static inline bool
relay_crypt_relay_forward(relay_crypto_t *crypto, cell_t *cell)
{
  switch (crypto->kind) {
    case RCK_TOR1:
      return tor1_crypt_relay_forward(&crypto->c.tor1, cell);
    case RCK_CGO: {
      const uint8_t *tag = NULL;
      cgo_crypt_relay_forward(crypto->c.cgo.fwd, cell, &tag);
      if (tag != NULL) {
        memcpy(crypto->c.cgo.last_tag, tag, SENDME_TAG_LEN_CGO);
        return true;
      } else {
        return false;
      }
    }
  }
  tor_assert_unreached();
}

/**
 * Handle relay-side backward encryption with crypto of an arbitrary type.
 */
static inline void
relay_crypt_relay_backward(relay_crypto_t *crypto, cell_t *cell)
{
  switch (crypto->kind) {
    case RCK_TOR1:
      tor1_crypt_relay_backward(&crypto->c.tor1, cell);
      break;
    case RCK_CGO: {
      cgo_crypt_relay_backward(crypto->c.cgo.back, cell);
      break;
    }
  }
}

/** Do the appropriate en/decryptions for <b>cell</b> arriving on
 * <b>circ</b> in direction <b>cell_direction</b>.
 *
 * If cell_direction == CELL_DIRECTION_IN:
 *   - If we're at the origin (we're the OP), for hops 1..N,
 *     decrypt cell. If recognized, stop.
 *   - Else (we're not the OP), encrypt one hop. Cell is not recognized.
 *
 * If cell_direction == CELL_DIRECTION_OUT:
 *   - decrypt one hop. Check if recognized.
 *
 * If cell is recognized, set *recognized to 1, and set
 * *layer_hint to the hop that recognized it.
 *
 * Return -1 to indicate that we should mark the circuit for close,
 * else return 0.
 */
int
relay_decrypt_cell(circuit_t *circ, cell_t *cell,
                   cell_direction_t cell_direction,
                   crypt_path_t **layer_hint, char *recognized)
{
  tor_assert(circ);
  tor_assert(cell);
  tor_assert(recognized);
  tor_assert(cell_direction == CELL_DIRECTION_IN ||
             cell_direction == CELL_DIRECTION_OUT);

  if (cell_direction == CELL_DIRECTION_IN) {
    if (CIRCUIT_IS_ORIGIN(circ)) { /* We're at the beginning of the circuit.
                                    * We'll want to do layered decrypts. */
      crypt_path_t *thishop, *cpath = TO_ORIGIN_CIRCUIT(circ)->cpath;
      thishop = cpath;
      if (thishop->state != CPATH_STATE_OPEN) {
        log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
               "Relay cell before first created cell? Closing.");
        return -1;
      }
      do { /* Remember: cpath is in forward order, that is, first hop first. */
        tor_assert(thishop);

        bool rec = relay_crypt_client_backward(&thishop->pvt_crypto, cell);
        if (rec) {
          *recognized = 1;
          *layer_hint = thishop;
          return 0;
        }
        thishop = thishop->next;
      } while (thishop != cpath && thishop->state == CPATH_STATE_OPEN);
      log_fn(LOG_PROTOCOL_WARN, LD_OR,
             "Incoming cell at client not recognized. Closing.");
      return -1;
    } else {
      /* We're in the middle. Encrypt one layer. */
      relay_crypto_t *crypto = &TO_OR_CIRCUIT(circ)->crypto;
      relay_crypt_relay_backward(crypto, cell);
    }
  } else /* cell_direction == CELL_DIRECTION_OUT */ {
    /* We're in the middle. Decrypt one layer. */
    relay_crypto_t *crypto = &TO_OR_CIRCUIT(circ)->crypto;

    bool rec = relay_crypt_relay_forward(crypto, cell);
    if (rec) {
      *recognized = 1;
      return 0;
    }
  }
  return 0;
}

/** Originate a client cell with a relay_crypt_t of arbitrary type. */
static inline void
relay_crypt_client_originate(relay_crypto_t *crypto, cell_t *cell)
{
  switch (crypto->kind) {
    case RCK_TOR1:
      tor1_crypt_client_originate(&crypto->c.tor1, cell);
      break;
    case RCK_CGO: {
      const uint8_t *tag = NULL;
      cgo_crypt_client_originate(crypto->c.cgo.fwd, cell, &tag);
      tor_assert(tag);
      memcpy(crypto->c.cgo.last_tag, tag, SENDME_TAG_LEN_CGO);
      break;
    }
  }
}

/** Perform forward-direction client encryption with a relay_crypt_t
 * of arbitrary type. */
static inline void
relay_crypt_client_forward(relay_crypto_t *crypto, cell_t *cell)
{
  switch (crypto->kind) {
    case RCK_TOR1:
      tor1_crypt_client_forward(&crypto->c.tor1, cell);
      break;
    case RCK_CGO:
      cgo_crypt_client_forward(crypto->c.cgo.fwd, cell);
      break;
  }
}

/**
 * Encrypt a cell <b>cell</b> that we are creating, and sending outbound on
 * <b>circ</b> until the hop corresponding to <b>layer_hint</b>.
 *
 * The integrity field and recognized field of <b>cell</b>'s relay headers
 * must be set to zero.
 */
void
relay_encrypt_cell_outbound(cell_t *cell,
                            origin_circuit_t *circ,
                            crypt_path_t *layer_hint)
{
  crypt_path_t *thishop = layer_hint;

  relay_crypt_client_originate(&thishop->pvt_crypto, cell);
  thishop = thishop->prev;

  while (thishop != circ->cpath->prev) {
    relay_crypt_client_forward(&thishop->pvt_crypto, cell);
    thishop = thishop->prev;
  }
}

/**
 * Encrypt a cell <b>cell</b> that we are creating, and sending on
 * <b>circuit</b> to the origin.
 *
 * The integrity field and recognized field of <b>cell</b>'s relay headers
 * must be set to zero.
 *
 * Returns 0 on success, -1 on error.
 */
int
relay_encrypt_cell_inbound(cell_t *cell,
                           or_circuit_t *or_circ)
{
  relay_crypto_t *crypto = &or_circ->crypto;
  switch (crypto->kind) {
    case RCK_TOR1:
      tor1_crypt_relay_originate(&crypto->c.tor1, cell);
      return 0;
    case RCK_CGO: {
      const uint8_t *tag = NULL;
      cgo_crypt_relay_originate(crypto->c.cgo.back, cell, &tag);
      tor_assert(tag);
      memcpy(&crypto->c.cgo.last_tag, tag, SENDME_TAG_LEN_CGO);
      return 0;
    }
    default:
      /* Do not silently accept unknown crypto algs as a no-op.
       * This mitigates the heap spray vector used in #41243.
       * (heap spray vectors in Tor are rare, so this is worthwhile). */
      tor_fragile_assert();
      memwipe(cell, 0, sizeof(*cell));
      return -1;
  }
}

/**
 * Release all storage held inside <b>crypto</b>, but do not free
 * <b>crypto</b> itself: it lives inside another object.
 */
void
relay_crypto_clear(relay_crypto_t *crypto)
{
  switch (crypto->kind) {
    case RCK_TOR1:
      tor1_crypt_clear(&crypto->c.tor1);
      break;
    case RCK_CGO:
      cgo_crypt_free(crypto->c.cgo.fwd);
      cgo_crypt_free(crypto->c.cgo.back);
      break;
  }
}

static int
cgo_pair_init(cgo_pair_t *pair, bool is_relay,
              const uint8_t *key_material, size_t key_data_len)
{
  memset(pair, 0, sizeof(*pair));
  const int aes_bits = CGO_AES_BITS;
  const size_t single_cgo_len = cgo_key_material_len(aes_bits);
  if (BUG(key_data_len != single_cgo_len * 2)) {
    return -1;
  }

  cgo_mode_t fwd_mode, back_mode;
  if (is_relay) {
    fwd_mode = CGO_MODE_RELAY_FORWARD;
    back_mode = CGO_MODE_RELAY_BACKWARD;
  } else {
    fwd_mode = CGO_MODE_CLIENT_FORWARD;
    back_mode = CGO_MODE_CLIENT_BACKWARD;
  }

  pair->fwd = cgo_crypt_new(fwd_mode, aes_bits,
                            key_material, single_cgo_len);
  pair->back = cgo_crypt_new(back_mode, aes_bits,
                             key_material + single_cgo_len, single_cgo_len);

  return 0;
}

/** Initialize <b>crypto</b> from the key material in key_data.
 *
 * If <b>is_hs_v3</b> is set, this cpath will be used for next gen hidden
 * service circuits and <b>key_data</b> must be at least
 * HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN bytes in length.
 *
 * If <b>is_hs_v3</b> is not set, key_data must contain CPATH_KEY_MATERIAL_LEN
 * bytes, which are used as follows:
 *   - 20 to initialize f_digest
 *   - 20 to initialize b_digest
 *   - 16 to key f_crypto
 *   - 16 to key b_crypto
 *
 * (If 'reverse' is true, then f_XX and b_XX are swapped.)
 *
 * Return 0 if init was successful, else -1 if it failed.
 */
int
relay_crypto_init(relay_crypto_alg_t alg,
                  relay_crypto_t *crypto,
                  const char *key_data, size_t key_data_len)
{
  switch (alg) {
    /* Tor1 cases: the booleans are "reverse" and "is_hs_v3". */
    case RELAY_CRYPTO_ALG_TOR1:
      crypto->kind = RCK_TOR1;
      return tor1_crypt_init(&crypto->c.tor1, key_data, key_data_len,
                             false, false);
    case RELAY_CRYPTO_ALG_TOR1_HSC:
      crypto->kind = RCK_TOR1;
      return tor1_crypt_init(&crypto->c.tor1, key_data, key_data_len,
                             false, true);
    case RELAY_CRYPTO_ALG_TOR1_HSS:
      crypto->kind = RCK_TOR1;
      return tor1_crypt_init(&crypto->c.tor1, key_data, key_data_len,
                             true, true);
    case RELAY_CRYPTO_ALG_CGO_CLIENT:
      crypto->kind = RCK_CGO;
      return cgo_pair_init(&crypto->c.cgo, false,
                           (const uint8_t *)key_data, key_data_len);
    case RELAY_CRYPTO_ALG_CGO_RELAY:
      crypto->kind = RCK_CGO;
      return cgo_pair_init(&crypto->c.cgo, true,
                           (const uint8_t *)key_data, key_data_len);
  }
  tor_assert_unreached();
}

/** Return the amount of key material we need to initialize
 * the given relay crypto algorithm.
 *
 * Return -1 if the algorithm is unrecognized.
 */
ssize_t
relay_crypto_key_material_len(relay_crypto_alg_t alg)
{
  switch (alg) {
    case RELAY_CRYPTO_ALG_TOR1:
      return tor1_key_material_len(false);
    case RELAY_CRYPTO_ALG_TOR1_HSC:
    case RELAY_CRYPTO_ALG_TOR1_HSS:
      return tor1_key_material_len(true);
    case RELAY_CRYPTO_ALG_CGO_CLIENT:
    case RELAY_CRYPTO_ALG_CGO_RELAY:
      return cgo_key_material_len(CGO_AES_BITS) * 2;
  }
  return -1;
}

/** Assert that <b>crypto</b> is valid and set. */
void
relay_crypto_assert_ok(const relay_crypto_t *crypto)
{
  switch (crypto->kind) {
    case RCK_TOR1:
      tor1_crypt_assert_ok(&crypto->c.tor1);
      break;
    case RCK_CGO:
      break;
  }
}
