/* * Copyright (c) 2016 Christian Huitema * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifdef _WINDOWS #include "wincompat.h" #else #include #endif #include #include #include #include "picotls.h" #include "picotls/minicrypto.h" #include "picotls/ffx.h" static void ffx_dispose(ptls_cipher_context_t *_ctx); static void ffx_encrypt(ptls_cipher_context_t *_ctx, void *output, const void *input, size_t len); static void ffx_init(struct st_ptls_cipher_context_t *ctx, const void *iv); int ptls_ffx_setup_crypto(ptls_cipher_context_t *_ctx, ptls_cipher_algorithm_t *algo, int is_enc, int nb_rounds, size_t bit_length, const void *key) { int ret = 0; ptls_ffx_context_t *ctx = (ptls_ffx_context_t *)_ctx; ptls_cipher_context_t *enc_ctx = NULL; size_t len = (bit_length + 7) / 8; uint8_t last_byte_mask[8] = {0xFF, 0xFE, 0xFC, 0xF8, 0xF0, 0xE0, 0xC0, 0x80}; assert(len <= 32 && len >= 2); assert(ctx->super.do_dispose == NULL); assert(ctx->super.do_init == NULL); assert(ctx->super.do_transform == NULL); assert(ctx->super.algo == NULL || algo->key_size == ctx->super.algo->key_size); assert(ctx->super.algo == NULL || algo->iv_size == ctx->super.algo->iv_size); assert(ctx->super.algo == NULL || ctx->super.algo->block_size == len); assert(algo->iv_size == 16); if (len <= 32 && len >= 2) { /* len must be lower than 32 */ enc_ctx = ptls_cipher_new(algo, 1, key); if (enc_ctx == NULL) { ret = PTLS_ERROR_LIBRARY; } } else { ret = PTLS_ERROR_LIBRARY; } if (ret == 0) { ctx->enc_ctx = enc_ctx; ctx->nb_rounds = nb_rounds; ctx->is_enc = is_enc; ctx->byte_length = len; ctx->nb_left = (int)len / 2; ctx->nb_right = (int)len - ctx->nb_left; ctx->mask_last_byte = last_byte_mask[bit_length % 8]; ptls_clear_memory(ctx->tweaks, sizeof(ctx->tweaks)); ctx->super.do_dispose = ffx_dispose; ctx->super.do_init = ffx_init; ctx->super.do_transform = ffx_encrypt; } else { ffx_dispose(_ctx); } return ret; } static void ffx_dispose(ptls_cipher_context_t *_ctx) { ptls_ffx_context_t *ctx = (ptls_ffx_context_t *)_ctx; assert(ctx->super.do_dispose == ffx_dispose); if (ctx->enc_ctx != NULL) { ptls_cipher_free(ctx->enc_ctx); } ctx->enc_ctx = NULL; ctx->nb_rounds = 0; ctx->byte_length = 0; ctx->nb_left = 0; ctx->nb_right = 0; ctx->mask_last_byte = 0; ctx->is_enc = 0; ctx->super.do_dispose = NULL; ctx->super.do_init = NULL; ctx->super.do_transform = NULL; } ptls_cipher_context_t *ptls_ffx_new(ptls_cipher_algorithm_t *algo, int is_enc, int nb_rounds, size_t bit_length, const void *key) { ptls_cipher_context_t *ctx = (ptls_cipher_context_t *)malloc(sizeof(ptls_ffx_context_t)); if (ctx != NULL) { memset(ctx, 0, sizeof(ptls_ffx_context_t)); if (ptls_ffx_setup_crypto(ctx, algo, is_enc, nb_rounds, bit_length, key) != 0) { free(ctx); ctx = NULL; } } return ctx; } static void ptls_ffx_one_pass(ptls_cipher_context_t *enc_ctx, uint8_t *source, size_t source_size, uint8_t *target, size_t target_size, uint8_t mask_last_byte, uint8_t *confusion, uint8_t *iv, uint8_t *tweaks, uint8_t round, uint8_t nb_rounds) { static const uint8_t zeros[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; memcpy(iv, tweaks, 16); iv[round & 15] ^= nb_rounds; for (size_t i = 0; i < source_size; i++) { iv[i] ^= source[i]; } ptls_cipher_init(enc_ctx, iv); ptls_cipher_encrypt(enc_ctx, confusion, zeros, 16); for (size_t j = 0; j < target_size - 1; j++) { target[j] ^= confusion[j]; } target[target_size - 1] ^= (confusion[target_size - 1] & mask_last_byte); } static void ffx_encrypt(ptls_cipher_context_t *_ctx, void *output, const void *input, size_t len) { ptls_ffx_context_t *ctx = (ptls_ffx_context_t *)_ctx; uint8_t left[16], right[16], confusion[32], iv[16]; uint8_t last_byte; assert(ctx->super.do_transform == ffx_encrypt); /* len must match context definition */ assert(len == ctx->byte_length); if (len != ctx->byte_length) { memset(output, 0, len); /* so that we do not leak anything in production mode */ return; } /* Split the input in two halves */ memcpy(left, input, ctx->nb_left); memcpy(right, ((uint8_t *)input) + ctx->nb_left, ctx->nb_right); memset(left + ctx->nb_left, 0, 16 - ctx->nb_left); memset(right + ctx->nb_right, 0, 16 - ctx->nb_right); last_byte = right[ctx->nb_right - 1]; right[ctx->nb_right - 1] &= ctx->mask_last_byte; if (ctx->is_enc) { /* Feistel construct, using the specified algorithm as S-Box */ for (int i = 0; i < ctx->nb_rounds; i += 2) { /* Each pass encrypts a zero field with a cipher using one * half of the message as IV. This construct lets us use * either AES or chacha 20 */ ptls_ffx_one_pass(ctx->enc_ctx, right, ctx->nb_right, left, ctx->nb_left, 0xFF, confusion, iv, ctx->tweaks, i, ctx->nb_rounds); ptls_ffx_one_pass(ctx->enc_ctx, left, ctx->nb_left, right, ctx->nb_right, ctx->mask_last_byte, confusion, iv, ctx->tweaks, i + 1, ctx->nb_rounds); } } else { /* Feistel construct, using the specified algorithm as S-Box, * in the opposite order of the encryption */ for (int i = 0; i < ctx->nb_rounds; i += 2) { /* Each pass encrypts a zero field with a cipher using one * half of the message as IV. This construct lets us use * either AES or chacha 20 */ ptls_ffx_one_pass(ctx->enc_ctx, left, ctx->nb_left, right, ctx->nb_right, ctx->mask_last_byte, confusion, iv, ctx->tweaks, ctx->nb_rounds - 1 - i, ctx->nb_rounds); ptls_ffx_one_pass(ctx->enc_ctx, right, ctx->nb_right, left, ctx->nb_left, 0xFF, confusion, iv, ctx->tweaks, ctx->nb_rounds - 2 - i, ctx->nb_rounds); } } /* After enough passes, we have a very strong length preserving * encryption, only that many times slower than the underlying * algorithm. We copy the result to the output */ memcpy(output, left, ctx->nb_left); right[ctx->nb_right - 1] &= ctx->mask_last_byte; right[ctx->nb_right - 1] |= (last_byte & ~ctx->mask_last_byte); memcpy(((uint8_t *)output) + ctx->nb_left, right, ctx->nb_right); ptls_clear_memory(left, sizeof(left)); ptls_clear_memory(right, sizeof(right)); ptls_clear_memory(confusion, sizeof(confusion)); } static void ffx_init(struct st_ptls_cipher_context_t *_ctx, const void *iv) { ptls_ffx_context_t *ctx = (ptls_ffx_context_t *)_ctx; assert(ctx->super.do_init == ffx_init); memcpy(ctx->tweaks, iv, 16); }