2018-05-10 14:46:36 +02:00
|
|
|
/* Copyright (c) 2018, The Tor Project, Inc. */
|
|
|
|
/* See LICENSE for licensing information */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A rudimentary order-preserving encryption scheme.
|
|
|
|
*
|
|
|
|
* To compute the encryption of N, this scheme uses an AES-CTR stream to
|
|
|
|
* generate M-byte values, and adds the first N of them together. (+1 each to
|
|
|
|
* insure that the ciphertexts are strictly decreasing.)
|
|
|
|
*
|
|
|
|
* We use this for generating onion service revision counters based on the
|
|
|
|
* current time, without leaking the amount of skew in our view of the current
|
|
|
|
* time. MUCH more analysis would be needed before using it for anything
|
|
|
|
* else!
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "orconfig.h"
|
|
|
|
|
|
|
|
#define CRYPTO_OPE_PRIVATE
|
2018-07-17 22:19:32 +02:00
|
|
|
#include "lib/crypt_ops/crypto_ope.h"
|
|
|
|
#include "lib/crypt_ops/crypto_util.h"
|
2018-08-01 01:56:42 +02:00
|
|
|
#include "lib/crypt_ops/crypto_cipher.h"
|
2018-07-17 22:19:32 +02:00
|
|
|
#include "lib/log/util_bug.h"
|
|
|
|
#include "lib/malloc/malloc.h"
|
|
|
|
#include "lib/arch/bytes.h"
|
|
|
|
|
|
|
|
#include <string.h>
|
2018-05-10 14:46:36 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* How infrequent should the precomputed values be for this encryption?
|
|
|
|
* The choice of this value creates a space/time tradeoff.
|
|
|
|
*
|
|
|
|
* Note that this value must be a multiple of 16; see
|
|
|
|
* ope_get_cipher()
|
|
|
|
*/
|
|
|
|
#define SAMPLE_INTERVAL 1024
|
|
|
|
/** Number of precomputed samples to make for each OPE key. */
|
|
|
|
#define N_SAMPLES (OPE_INPUT_MAX / SAMPLE_INTERVAL)
|
|
|
|
|
2018-07-17 22:19:32 +02:00
|
|
|
struct crypto_ope_t {
|
2018-05-10 14:46:36 +02:00
|
|
|
/** An AES key for use with this object. */
|
|
|
|
uint8_t key[OPE_KEY_LEN];
|
|
|
|
/** Cached intermediate encryption values at SAMPLE_INTERVAL,
|
|
|
|
* SAMPLE_INTERVAL*2,...SAMPLE_INTERVAL*N_SAMPLES */
|
|
|
|
uint64_t samples[N_SAMPLES];
|
|
|
|
};
|
|
|
|
|
|
|
|
/** The type to add up in order to produce our OPE ciphertexts */
|
|
|
|
typedef uint16_t ope_val_t;
|
|
|
|
|
|
|
|
#ifdef WORDS_BIG_ENDIAN
|
|
|
|
/** Convert an OPE value to little-endian */
|
|
|
|
static inline ope_val_t
|
|
|
|
ope_val_to_le(ope_val_t x)
|
|
|
|
{
|
|
|
|
return
|
|
|
|
((x) >> 8) |
|
|
|
|
(((x)&0xff) << 8);
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
#define ope_val_to_le(x) (x)
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return a new AES256-CTR stream cipher object for <b>ope</b>, ready to yield
|
|
|
|
* bytes from the stream at position <b>initial_idx</b>.
|
|
|
|
*
|
|
|
|
* Note that because the index is converted directly to an IV, it must be a
|
|
|
|
* multiple of the AES block size (16).
|
|
|
|
*/
|
|
|
|
STATIC crypto_cipher_t *
|
|
|
|
ope_get_cipher(const crypto_ope_t *ope, uint32_t initial_idx)
|
|
|
|
{
|
|
|
|
uint8_t iv[CIPHER_IV_LEN];
|
|
|
|
tor_assert((initial_idx & 0xf) == 0);
|
2018-07-17 22:19:32 +02:00
|
|
|
uint32_t n = tor_htonl(initial_idx >> 4);
|
2018-05-10 14:46:36 +02:00
|
|
|
memset(iv, 0, sizeof(iv));
|
|
|
|
memcpy(iv + CIPHER_IV_LEN - sizeof(n), &n, sizeof(n));
|
|
|
|
|
|
|
|
return crypto_cipher_new_with_iv_and_bits(ope->key,
|
|
|
|
iv,
|
|
|
|
OPE_KEY_LEN * 8);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieve and add the next <b>n</b> values from the stream cipher <b>c</b>,
|
|
|
|
* and return their sum.
|
|
|
|
*
|
|
|
|
* Note that values are taken in little-endian order (for performance on
|
|
|
|
* prevalent hardware), and are mapped from range 0..2^n-1 to range 1..2^n (so
|
|
|
|
* that each input encrypts to a different output).
|
|
|
|
*
|
|
|
|
* NOTE: this function is not constant-time.
|
|
|
|
*/
|
|
|
|
STATIC uint64_t
|
|
|
|
sum_values_from_cipher(crypto_cipher_t *c, size_t n)
|
|
|
|
{
|
|
|
|
#define BUFSZ 256
|
|
|
|
ope_val_t buf[BUFSZ];
|
|
|
|
uint64_t total = 0;
|
|
|
|
unsigned i;
|
|
|
|
while (n >= BUFSZ) {
|
|
|
|
memset(buf, 0, sizeof(buf));
|
|
|
|
crypto_cipher_crypt_inplace(c, (char*)buf, BUFSZ*sizeof(ope_val_t));
|
|
|
|
|
|
|
|
for (i = 0; i < BUFSZ; ++i) {
|
|
|
|
total += ope_val_to_le(buf[i]);
|
|
|
|
total += 1;
|
|
|
|
}
|
|
|
|
n -= BUFSZ;
|
|
|
|
}
|
|
|
|
|
|
|
|
memset(buf, 0, n*sizeof(ope_val_t));
|
|
|
|
crypto_cipher_crypt_inplace(c, (char*)buf, n*sizeof(ope_val_t));
|
|
|
|
for (i = 0; i < n; ++i) {
|
|
|
|
total += ope_val_to_le(buf[i]);
|
|
|
|
total += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
memset(buf, 0, sizeof(buf));
|
|
|
|
return total;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return a new crypto_ope_t object, using the provided 256-bit key.
|
|
|
|
*/
|
|
|
|
crypto_ope_t *
|
|
|
|
crypto_ope_new(const uint8_t *key)
|
|
|
|
{
|
|
|
|
crypto_ope_t *ope = tor_malloc_zero(sizeof(crypto_ope_t));
|
|
|
|
memcpy(ope->key, key, OPE_KEY_LEN);
|
|
|
|
|
|
|
|
crypto_cipher_t *cipher = ope_get_cipher(ope, 0);
|
|
|
|
|
|
|
|
uint64_t v = 0;
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < N_SAMPLES; ++i) {
|
|
|
|
v += sum_values_from_cipher(cipher, SAMPLE_INTERVAL);
|
|
|
|
ope->samples[i] = v;
|
|
|
|
}
|
|
|
|
|
|
|
|
crypto_cipher_free(cipher);
|
|
|
|
return ope;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Free all storage held in <>ope</b>. */
|
|
|
|
void
|
|
|
|
crypto_ope_free_(crypto_ope_t *ope)
|
|
|
|
{
|
|
|
|
if (!ope)
|
|
|
|
return;
|
|
|
|
memwipe(ope, 0, sizeof(*ope));
|
|
|
|
tor_free(ope);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return the encrypted value corresponding to <b>input</b>. The input value
|
2018-07-10 19:10:22 +02:00
|
|
|
* must be in range 1..OPE_INPUT_MAX. Returns CRYPTO_OPE_ERROR on an invalid
|
|
|
|
* input.
|
2018-05-10 14:46:36 +02:00
|
|
|
*
|
|
|
|
* NOTE: this function is not constant-time.
|
|
|
|
*/
|
|
|
|
uint64_t
|
|
|
|
crypto_ope_encrypt(const crypto_ope_t *ope, int plaintext)
|
|
|
|
{
|
|
|
|
if (plaintext <= 0 || plaintext > OPE_INPUT_MAX)
|
2018-07-10 19:10:22 +02:00
|
|
|
return CRYPTO_OPE_ERROR;
|
2018-05-10 14:46:36 +02:00
|
|
|
|
|
|
|
const int sample_idx = (plaintext / SAMPLE_INTERVAL);
|
|
|
|
const int starting_iv = sample_idx * SAMPLE_INTERVAL;
|
|
|
|
const int remaining_values = plaintext - starting_iv;
|
|
|
|
uint64_t v;
|
|
|
|
if (sample_idx == 0) {
|
|
|
|
v = 0;
|
|
|
|
} else {
|
|
|
|
v = ope->samples[sample_idx - 1];
|
|
|
|
}
|
|
|
|
crypto_cipher_t *cipher = ope_get_cipher(ope, starting_iv*sizeof(ope_val_t));
|
|
|
|
|
|
|
|
v += sum_values_from_cipher(cipher, remaining_values);
|
|
|
|
|
|
|
|
crypto_cipher_free(cipher);
|
|
|
|
|
|
|
|
return v;
|
|
|
|
}
|