Browse Source

changed aes transform to cipher text stealing mode

pull/380/head
Logan007 4 years ago
parent
commit
6d05d48878
  1. 1
      CMakeLists.txt
  2. 10
      doc/Crypto.md
  3. 63
      include/aes.h
  4. 1
      include/n2n.h
  5. 9
      include/n2n_wire.h
  6. 201
      src/aes.c
  7. 501
      src/transform_aes.c
  8. 22
      src/wire.c

1
CMakeLists.txt

@ -102,6 +102,7 @@ add_library(n2n STATIC
src/transform_aes.c
src/transform_cc20.c
src/transform_speck.c
src/aes.c
src/speck.c
src/random_numbers.c
src/pearson.c

10
doc/Crypto.md

@ -18,11 +18,11 @@ The following chart might help to make a quick comparison and decide what cipher
| Cipher | Mode | Block Size | Key Size | IV length |Speed | Built-In | Origin |
| :---: | :---:| :---: | :---: | :---: |:---: | :---: | --- |
|Twofish | CTS | 128 bits | 256 bit | 32 bit | - | Y | Bruce Schneier |
|AES | CBC | 128 bits | 128, 192, 256 bit| 64 bit | O..+ | N | Joan Daemen, Vincent Rijmen, NSA-approved |
|AES | CBC | 128 bits | 128, 192, 256 bit| 128 bit | O..+ | N | Joan Daemen, Vincent Rijmen, NSA-approved |
|ChaCha20| CTR | Stream | 256 bit | 128 bit | +..++| N | Daniel J. Bernstein |
|SPECK | CTR | Stream | 256 bit | 128 bit | ++ | Y | NSA |
The two block ciphers Twofish and AES are used in CTS mode (Twofish) and CBC mode(AES). AES requires a padding which results in encrypted payload size modulo their blocksize. Sizewise, this could be considered as a disadvantage. On the other hand, stream ciphers need a longer initialization vector (IV) to be transmitted with the cipher.
The two block ciphers Twofish and AES are used in CTS mode.
Note that AES and ChaCha20 are available only if n2n is compiled with openSSL support. n2n will work well without them offering the respectively reduced choice of remaining built-in ciphers (Twofish, SPECK).
@ -38,16 +38,12 @@ _We might try to find a faster implementation._
### AES
AES uses the standard way of an IV but it does not neccessarily transmit the full IV along with the packets. The size of the transmitted part is adjustable by changing the `TRANSOP_AES_IV_SEED_SIZE` definition found in `src/transform_aes.c`. It defaults to 8 meaning that 8 bytes (of max 16) are transmitted. The remaining 8 bytes are fixed, key-derived material is used to fill up to full block size. A single AES-ECB encryption step is applied to these 16 bytes before they get used as regular IV for AES-CBCing the payload.
Padding to the last block happens by filling `0x00`-bytes and indicating their number as the last byte of the block. This could lead to up to 16 extra bytes.
AES also prepends a random value to the plaintext. Its size is adjustable by changing the `AES_PREAMBLE_SIZE` definition found in `src/transform_aes.c`. It defaults to AES_BLOCK_SIZE (== 16). The AES scheme uses a CBC/CTS scheme which can send out plaintext-length ciphertexts as long as they are one block or more in length.
AES relies on openSSL's `evp_*` interface which also offers hardware acceleration where available (SSE, AES-NI, …). It however is slower than the following stream ciphers because the CBC mode cannot compete with the optimized stream ciphers.
_Perhaps, AES-CTR being a stream cipher could have competed with the stream ciphers._
_Another possible extension would be to bring CTS mode to AES in some future version, just to avoid unneccessary weight gains from padding. CTS mode works well starting with plain texts from one block plus. So, we might revert back to the Twofish-way of IV handling with a full block IV._
### ChaCha20
ChaCha20 was the first stream cipher supported by n2n.

63
include/aes.h

@ -0,0 +1,63 @@
/**
* (C) 2007-20 - ntop.org and contributors
*
* This program 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 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not see see <http://www.gnu.org/licenses/>
*
*/
#ifdef N2N_HAVE_AES
#ifndef AES_H
#define AES_H
#include <stdint.h>
#include <openssl/aes.h>
#include <openssl/evp.h>
#include <openssl/err.h>
#define AES256_KEY_BYTES (256/8)
#define AES192_KEY_BYTES (192/8)
#define AES128_KEY_BYTES (128/8)
typedef struct aes_context_t {
#ifdef HAVE_OPENSSL_1_1
EVP_CIPHER_CTX *enc_ctx; /* openssl's reusable evp_* en/de-cryption context */
EVP_CIPHER_CTX *dec_ctx; /* openssl's reusable evp_* en/de-cryption context */
const EVP_CIPHER *cipher; /* cipher to use: e.g. EVP_aes_128_cbc */
uint8_t key[AES256_KEY_BYTES]; /* the pure key data for payload encryption & decryption */
AES_KEY ecb_dec_key; /* one step ecb decryption key */
#else
AES_KEY enc_key; /* tx key */
AES_KEY dec_key; /* tx key */
#endif
} aes_context_t;
int aes_cbc_encrypt (unsigned char *out, const unsigned char *in, size_t in_len,
const unsigned char *iv, aes_context_t *ctx);
int aes_cbc_decrypt (unsigned char *out, const unsigned char *in, size_t in_len,
const unsigned char *iv, aes_context_t *ctx);
int aes_ecb_decrypt (unsigned char *out, const unsigned char *in, aes_context_t *ctx);
int aes_init (const unsigned char *key, size_t key_size, aes_context_t **ctx);
#endif // AES_H
#endif // N2N_HAVE_AES

1
include/n2n.h

@ -159,6 +159,7 @@ typedef struct ether_hdr ether_hdr_t;
#include "random_numbers.h"
#include "pearson.h"
#include "portable_endian.h"
#include "aes.h"
#include "speck.h"
#include "n2n_regex.h"

9
include/n2n_wire.h

@ -221,6 +221,15 @@ int decode_uint32( uint32_t * out,
size_t * rem,
size_t * idx );
int encode_uint64( uint8_t * base,
size_t * idx,
const uint64_t v );
int decode_uint64( uint64_t * out,
const uint8_t * base,
size_t * rem,
size_t * idx );
int encode_buf( uint8_t * base,
size_t * idx,
const void * p,

201
src/aes.c

@ -0,0 +1,201 @@
/**
* (C) 2007-20 - ntop.org and contributors
*
* This program 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 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not see see <http://www.gnu.org/licenses/>
*
*/
#include "n2n.h"
#ifdef N2N_HAVE_AES
/* ****************************************************** */
#ifdef HAVE_OPENSSL_1_1
// get any erorr message out of openssl
// taken from https://en.wikibooks.org/wiki/OpenSSL/Error_handling
static char *openssl_err_as_string (void) {
BIO *bio = BIO_new (BIO_s_mem ());
ERR_print_errors (bio);
char *buf = NULL;
size_t len = BIO_get_mem_data (bio, &buf);
char *ret = (char *) calloc (1, 1 + len);
if(ret)
memcpy (ret, buf, len);
BIO_free (bio);
return ret;
}
#endif
/* ****************************************************** */
int aes_cbc_encrypt (unsigned char *out, const unsigned char *in, size_t in_len,
const unsigned char *iv, aes_context_t *ctx) {
#ifdef HAVE_OPENSSL_1_1
int evp_len;
int evp_ciphertext_len;
if(1 == EVP_EncryptInit_ex(ctx->enc_ctx, ctx->cipher, NULL, ctx->key, iv)) {
if(1 == EVP_CIPHER_CTX_set_padding(ctx->enc_ctx, 0)) {
if(1 == EVP_EncryptUpdate(ctx->enc_ctx, out, &evp_len, in, in_len)) {
evp_ciphertext_len = evp_len;
if(1 == EVP_EncryptFinal_ex(ctx->enc_ctx, out + evp_len, &evp_len)) {
evp_ciphertext_len += evp_len;
if(evp_ciphertext_len != in_len)
traceEvent(TRACE_ERROR, "aes_cbc_encrypt openssl encryption: encrypted %u bytes where %u were expected",
evp_ciphertext_len, in_len);
} else
traceEvent(TRACE_ERROR, "aes_cbc_encrypt openssl final encryption: %s",
openssl_err_as_string());
} else
traceEvent(TRACE_ERROR, "aes_cbc_encrypt openssl encrpytion: %s",
openssl_err_as_string());
} else
traceEvent(TRACE_ERROR, "aes_cbc_encrypt openssl padding setup: %s",
openssl_err_as_string());
} else
traceEvent(TRACE_ERROR, "aes_cbc_encrypt openssl init: %s",
openssl_err_as_string());
EVP_CIPHER_CTX_reset(ctx->enc_ctx);
#else
AES_cbc_encrypt(in, // source
out, // destination
in_len, // enc size
&(ctx->enc_key),
iv,
AES_ENCRYPT);
#endif
}
/* ****************************************************** */
int aes_cbc_decrypt (unsigned char *out, const unsigned char *in, size_t in_len,
const unsigned char *iv, aes_context_t *ctx) {
#ifdef HAVE_OPENSSL_1_1
int evp_len;
int evp_plaintext_len;
if(1 == EVP_DecryptInit_ex(ctx->dec_ctx, ctx->cipher, NULL, ctx->key, iv)) {
if(1 == EVP_CIPHER_CTX_set_padding(ctx->dec_ctx, 0)) {
if(1 == EVP_DecryptUpdate(ctx->dec_ctx, out, &evp_len, in, in_len)) {
evp_plaintext_len = evp_len;
if(1 == EVP_DecryptFinal_ex(ctx->dec_ctx, out + evp_len, &evp_len)) {
evp_plaintext_len += evp_len;
if(evp_plaintext_len != in_len)
traceEvent(TRACE_ERROR, "aes_cbc_decrypt openssl decryption: decrypted %u bytes where %u were expected",
evp_plaintext_len, in_len);
} else
traceEvent(TRACE_ERROR, "aes_cbc_decrypt openssl final decryption: %s",
openssl_err_as_string());
} else
traceEvent(TRACE_ERROR, "aes_cbc_decrypt openssl decrpytion: %s",
openssl_err_as_string());
} else
traceEvent(TRACE_ERROR, "aes_cbc_decrypt openssl padding setup: %s",
openssl_err_as_string());
} else
traceEvent(TRACE_ERROR, "aes_cbc_decrypt openssl init: %s",
openssl_err_as_string());
EVP_CIPHER_CTX_reset(ctx->dec_ctx);
#else
AES_cbc_encrypt(in, // source
out, // destination
in_len, // enc size
&(ctx->dec_key),
iv,
AES_DECRYPT);
#endif
return 0;
}
/* ****************************************************** */
int aes_ecb_decrypt (unsigned char *out, const unsigned char *in, aes_context_t *ctx) {
#ifdef HAVE_OPENSSL_1_1
AES_ecb_encrypt(in, out, &(ctx->ecb_dec_key), AES_DECRYPT);
#else
AES_ecb_encrypt(in, out, &(ctx->dec_key), AES_DECRYPT);
#endif
}
/* ****************************************************** */
int aes_init (const unsigned char *key, size_t key_size, aes_context_t **ctx) {
// allocate context...
*ctx = (aes_context_t*) calloc(1, sizeof(aes_context_t));
if (!(*ctx))
return -1;
// ...and fill her up
// initialize data structures
#ifdef HAVE_OPENSSL_1_1
if(!((*ctx)->enc_ctx = EVP_CIPHER_CTX_new())) {
traceEvent(TRACE_ERROR, "aes_init openssl's evp_* encryption context creation failed: %s",
openssl_err_as_string());
return(-1);
}
if(!((*ctx)->dec_ctx = EVP_CIPHER_CTX_new())) {
traceEvent(TRACE_ERROR, "aes_init openssl's evp_* decryption context creation failed: %s",
openssl_err_as_string());
return(-1);
}
#endif
// check key size and make key size (given in bytes) dependant settings
switch(key_size) {
case AES128_KEY_BYTES: // 128 bit key size
#ifdef HAVE_OPENSSL_1_1
(*ctx)->cipher = EVP_aes_128_cbc();
#endif
break;
case AES192_KEY_BYTES: // 192 bit key size
#ifdef HAVE_OPENSSL_1_1
(*ctx)->cipher = EVP_aes_192_cbc();
#endif
break;
case AES256_KEY_BYTES: // 256 bit key size
#ifdef HAVE_OPENSSL_1_1
(*ctx)->cipher = EVP_aes_256_cbc();
#endif
break;
default:
traceEvent(TRACE_ERROR, "aes_init invalid key size %u\n", key_size);
return -1;
}
// key materiel handling
#ifdef HAVE_OPENSSL_1_1
memcpy((*ctx)->key, key, key_size);
AES_set_decrypt_key(key, key_size * 8, &((*ctx)->ecb_dec_key));
#else
AES_set_encrypt_key(key, key_size * 8, &((*ctx)->enc_key));
AES_set_decrypt_key(key, key_size * 8, &((*ctx)->dec_key));
#endif
return 0;
}
#endif // N2N_HAVE_AES

501
src/transform_aes.c

@ -16,43 +16,29 @@
*
*/
#include "n2n.h"
#ifdef N2N_HAVE_AES
#include <openssl/aes.h>
#include <openssl/sha.h>
#include <openssl/evp.h>
#include <openssl/err.h>
#define N2N_AES_IVEC_SIZE (AES_BLOCK_SIZE)
#define AES_BLOCK_SIZE 16
#define AES256_KEY_BYTES (256/8)
#define AES192_KEY_BYTES (192/8)
#define AES128_KEY_BYTES (128/8)
// size of random value prepended to plaintext defaults to AES BLOCK_SIZE;
// gradually abandoning security, lower values could be chosen;
// however, minimum transmission size with cipher text stealing scheme is one
// block; as network packets should be longer anyway, only low level programmer
// might encounter an issue with lower values here
#define AES_PREAMBLE_SIZE (AES_BLOCK_SIZE)
/* AES plaintext preamble */
#define TRANSOP_AES_IV_SEED_SIZE 8 /* size of transmitted random part of IV in bytes; could range
* from 0=lowest security (constant IV) to 16=higest security
* (fully random IV); default=8 */
#define TRANSOP_AES_IV_PADDING_SIZE (N2N_AES_IVEC_SIZE - TRANSOP_AES_IV_SEED_SIZE)
#define TRANSOP_AES_IV_KEY_BYTES (AES128_KEY_BYTES) /* use AES128 for IV encryption */
#define TRANSOP_AES_PREAMBLE_SIZE (TRANSOP_AES_IV_SEED_SIZE)
#define AES_IV_SIZE (AES_BLOCK_SIZE)
typedef unsigned char n2n_aes_ivec_t[N2N_AES_IVEC_SIZE];
// cbc mode is being used with random value prepended to plaintext
// instead of iv so, actual iv is null_iv
const uint8_t null_iv[AES_IV_SIZE] = {0};
typedef struct transop_aes {
#ifdef HAVE_OPENSSL_1_1
EVP_CIPHER_CTX *enc_ctx; /* openssl's reusable evp_* encryption context */
EVP_CIPHER_CTX *dec_ctx; /* openssl's reusable evp_* decryption context */
const EVP_CIPHER *cipher; /* cipher to use: e.g. EVP_aes_128_cbc */
uint8_t key[32]; /* the pure key data for payload encryption & decryption */
#else
AES_KEY enc_key; /* tx key */
AES_KEY dec_key; /* tx key */
#endif
AES_KEY iv_enc_key; /* key used to encrypt the IV */
uint8_t iv_pad_val[TRANSOP_AES_IV_PADDING_SIZE]; /* key used to pad the random IV seed to full block size */
aes_context_t *ctx;
} transop_aes_t;
/* ****************************************************** */
@ -60,364 +46,174 @@ typedef struct transop_aes {
static int transop_deinit_aes(n2n_trans_op_t *arg) {
transop_aes_t *priv = (transop_aes_t *)arg->priv;
#ifdef HAVE_OPENSSL_1_1
EVP_CIPHER_CTX_free(priv->enc_ctx);
EVP_CIPHER_CTX_free(priv->dec_ctx);
#endif
if(priv->ctx) free(priv->ctx);
if(priv)
free(priv);
if(priv) free(priv);
return 0;
}
/* ****************************************************** */
#ifdef HAVE_OPENSSL_1_1
/* get any erorr message out of openssl
taken from https://en.wikibooks.org/wiki/OpenSSL/Error_handling */
static char *openssl_err_as_string (void) {
BIO *bio = BIO_new (BIO_s_mem ());
ERR_print_errors (bio);
char *buf = NULL;
size_t len = BIO_get_mem_data (bio, &buf);
char *ret = (char *) calloc (1, 1 + len);
if(ret)
memcpy (ret, buf, len);
BIO_free (bio);
return ret;
}
#endif
/* ****************************************************** */
/* convert a given number of bytes from memory to hex string; taken (and modified) from
https://stackoverflow.com/questions/6357031/how-do-you-convert-a-byte-array-to-a-hexadecimal-string-in-c */
const char* to_hex(unsigned char * in, size_t insz, char * out, size_t outsz)
{
unsigned char * pin = in;
const char * hex = "0123456789abcdef";
char * pout = out;
for(; pin < in+insz; pout +=2, pin++){
pout[0] = hex[(*pin>>4) & 0xF];
pout[1] = hex[ *pin & 0xF];
if (pout + 2 - out > outsz){
/* Better to truncate output string than overflow buffer */
/* it would be still better to either return a status */
/* or ensure the target buffer is large enough and it never happen */
break;
}
}
pout[2] = 0;
return out;
}
/* ****************************************************** */
static void set_aes_cbc_iv(transop_aes_t *priv, n2n_aes_ivec_t ivec, uint8_t * iv_seed) {
uint8_t iv_full[N2N_AES_IVEC_SIZE];
/* Extend the seed to full block size with padding value */
memcpy(iv_full, priv->iv_pad_val, TRANSOP_AES_IV_PADDING_SIZE);
memcpy(iv_full + TRANSOP_AES_IV_PADDING_SIZE, iv_seed, TRANSOP_AES_IV_SEED_SIZE);
/* Encrypt the IV with secret key to make it unpredictable.
* As discussed in https://github.com/ntop/n2n/issues/72, it's important to
* have an unpredictable IV since the initial part of the packet plaintext
* can be easily reconstructed from plaintext headers and used by an attacker
* to perform differential analysis.
*/
AES_ecb_encrypt(iv_full, ivec, &priv->iv_enc_key, AES_ENCRYPT);
}
/* ****************************************************** */
/** The aes packet format consists of:
*
* - a TRANSOP_AES_IV_SEED_SIZE-sized [bytes] random IV seed
* - encrypted payload.
*
* [II|DDDDDDDDDDDDDDDDDDDDD]
* |<---- encrypted ---->|
*/
// the aes packet format consists of
//
// - a random AES_PREAMBLE_SIZE-sized value prepended to plaintext
// encrypted together with the...
// - ... payload data
//
// [VV|DDDDDDDDDDDDDDDDDDDDD]
// | <---- encrypted ----> |
//
static int transop_encode_aes(n2n_trans_op_t * arg,
uint8_t * outbuf,
size_t out_len,
const uint8_t * inbuf,
size_t in_len,
const uint8_t * peer_mac) {
int len2=-1;
transop_aes_t * priv = (transop_aes_t *)arg->priv;
uint8_t assembly[N2N_PKT_BUF_SIZE] = {0};
// the assembly buffer is a source for encrypting data
// the whole contents of assembly are encrypted
uint8_t assembly[N2N_PKT_BUF_SIZE];
size_t idx = 0;
int padded_len;
uint8_t padding;
uint8_t buf[AES_BLOCK_SIZE];
if(in_len <= N2N_PKT_BUF_SIZE) {
if((in_len + TRANSOP_AES_PREAMBLE_SIZE) <= out_len) {
int len=-1;
size_t idx=0;
uint8_t iv_seed[TRANSOP_AES_IV_SEED_SIZE];
uint8_t padding = 0;
n2n_aes_ivec_t enc_ivec = {0};
traceEvent(TRACE_DEBUG, "encode_aes %lu", in_len);
/* Generate and encode the IV seed using as many calls to n2n_rand() as neccessary.
* Note: ( N2N_AES_IV_SEED_SIZE % sizeof(rand_value) ) not neccessarily equals 0. */
uint64_t rand_value;
int8_t i;
for (i = TRANSOP_AES_IV_SEED_SIZE; i >= sizeof(rand_value); i -= sizeof(rand_value)) {
rand_value = n2n_rand();
memcpy(iv_seed + TRANSOP_AES_IV_SEED_SIZE - i, &rand_value, sizeof(rand_value));
}
/* Are there bytes left to fill? */
if (i != 0) {
rand_value = n2n_rand();
memcpy(iv_seed, &rand_value, i);
}
encode_buf(outbuf, &idx, iv_seed, TRANSOP_AES_IV_SEED_SIZE);
/* Encrypt the assembly contents and write the ciphertext after the iv seed. */
/* len is set to the length of the cipher plain text to be encrpyted
which is (in this case) identical to original packet lentgh */
len = in_len;
/* The assembly buffer is a source for encrypting data.
* The whole contents of assembly are encrypted. */
memcpy(assembly, inbuf, in_len);
/* Need at least one encrypted byte at the end for the padding. */
len2 = ((len / AES_BLOCK_SIZE) + 1) * AES_BLOCK_SIZE; /* Round up to next whole AES adding at least one byte. */
padding = (len2-len);
assembly[len2 - 1] = padding;
char iv_seed_hex[2 * N2N_AES_IVEC_SIZE + 1];
traceEvent(TRACE_DEBUG, "padding = %u, seed = 0x%s", padding, to_hex (iv_seed, TRANSOP_AES_IV_SEED_SIZE, iv_seed_hex, 2 * N2N_AES_IVEC_SIZE + 1) );
set_aes_cbc_iv(priv, enc_ivec, iv_seed);
#ifdef HAVE_OPENSSL_1_1
EVP_CIPHER_CTX *ctx = priv->enc_ctx;
int evp_len;
int evp_ciphertext_len;
if(1 == EVP_EncryptInit_ex(ctx, priv->cipher, NULL, priv->key, enc_ivec)) {
if(1 == EVP_CIPHER_CTX_set_padding(ctx, 0)) {
if(1 == EVP_EncryptUpdate(ctx, outbuf + TRANSOP_AES_PREAMBLE_SIZE, &evp_len, assembly, len2)) {
evp_ciphertext_len = evp_len;
if(1 == EVP_EncryptFinal_ex(ctx, outbuf + TRANSOP_AES_PREAMBLE_SIZE + evp_len, &evp_len)) {
evp_ciphertext_len += evp_len;
if(evp_ciphertext_len != len2)
traceEvent(TRACE_ERROR, "encode_aes openssl encryption: encrypted %u bytes where %u were expected.\n",
evp_ciphertext_len, len2);
} else
traceEvent(TRACE_ERROR, "encode_aes openssl final encryption: %s\n", openssl_err_as_string());
} else
traceEvent(TRACE_ERROR, "encode_aes openssl encrpytion: %s\n", openssl_err_as_string());
} else
traceEvent(TRACE_ERROR, "encode_aes openssl padding setup: %s\n", openssl_err_as_string());
} else
traceEvent(TRACE_ERROR, "encode_aes openssl init: %s\n", openssl_err_as_string());
EVP_CIPHER_CTX_reset(ctx);
#else
AES_cbc_encrypt(assembly, /* source */
outbuf + TRANSOP_AES_PREAMBLE_SIZE, /* dest */
len2, /* enc size */
&(priv->enc_key), enc_ivec, AES_ENCRYPT);
#endif
len2 += TRANSOP_AES_PREAMBLE_SIZE; /* size of data carried in UDP. */
if((in_len + AES_PREAMBLE_SIZE + AES_BLOCK_SIZE) <= out_len) {
traceEvent(TRACE_DEBUG, "transop_encode_aes %lu bytes plaintext", in_len);
// full block sized random value (128 bit)
encode_uint64(assembly, &idx, n2n_rand());
encode_uint64(assembly, &idx, n2n_rand());
// adjust for maybe differently chosen AES_PREAMBLE_SIZE
idx = AES_PREAMBLE_SIZE;
// the plaintext data
encode_buf(assembly, &idx, inbuf, in_len);
// round up to next whole AES block size
padded_len = (((idx - 1) / AES_BLOCK_SIZE) + 1) * AES_BLOCK_SIZE;
padding = (padded_len-idx);
// pad the following bytes with zero, fixed length (AES_BLOCK_SIZE) seems to compile
// to slightly faster code than run-time dependant 'padding'
memset (assembly + idx, 0, AES_BLOCK_SIZE);
aes_cbc_encrypt(outbuf, assembly, padded_len, null_iv, priv->ctx);
if(padding) {
// exchange last two cipher blocks
memcpy (buf, outbuf+padded_len - AES_BLOCK_SIZE, AES_BLOCK_SIZE);
memcpy (outbuf + padded_len - AES_BLOCK_SIZE, outbuf + padded_len - 2 * AES_BLOCK_SIZE, AES_BLOCK_SIZE);
memcpy (outbuf + padded_len - 2 * AES_BLOCK_SIZE, buf, AES_BLOCK_SIZE);
}
} else
traceEvent(TRACE_ERROR, "encode_aes outbuf too small.");
traceEvent(TRACE_ERROR, "transop_encode_aes outbuf too small");
} else
traceEvent(TRACE_ERROR, "encode_aes inbuf too big to encrypt.");
traceEvent(TRACE_ERROR, "transop_encode_aes inbuf too big to encrypt");
return len2;
return idx;
}
/* ****************************************************** */
/* See transop_encode_aes for packet format */
// see transop_encode_aes for packet format
static int transop_decode_aes(n2n_trans_op_t * arg,
uint8_t * outbuf,
size_t out_len,
const uint8_t * inbuf,
size_t in_len,
const uint8_t * peer_mac) {
int len=0;
transop_aes_t * priv = (transop_aes_t *)arg->priv;
uint8_t assembly[N2N_PKT_BUF_SIZE];
if(((in_len - TRANSOP_AES_PREAMBLE_SIZE) <= N2N_PKT_BUF_SIZE) /* Cipher text fits in assembly */
&& (in_len >= TRANSOP_AES_PREAMBLE_SIZE) /* Has at least iv seed */
)
{
size_t rem=in_len;
size_t idx=0;
uint8_t iv_seed[TRANSOP_AES_IV_SEED_SIZE];
/* Get the IV seed */
decode_buf((uint8_t *)&iv_seed, TRANSOP_AES_IV_SEED_SIZE, inbuf, &rem, &idx);
char iv_seed_hex[2 * N2N_AES_IVEC_SIZE + 1];
traceEvent(TRACE_DEBUG, "decode_aes %lu with seed 0x%s", in_len, to_hex (iv_seed, TRANSOP_AES_IV_SEED_SIZE, iv_seed_hex, 2 * N2N_AES_IVEC_SIZE + 1) );
len = (in_len - TRANSOP_AES_PREAMBLE_SIZE);
if(0 == (len % AES_BLOCK_SIZE)) {
uint8_t padding;
n2n_aes_ivec_t dec_ivec = {0};
set_aes_cbc_iv(priv, dec_ivec, iv_seed);
#ifdef HAVE_OPENSSL_1_1
EVP_CIPHER_CTX *ctx = priv->dec_ctx;
int evp_len;
int evp_plaintext_len;
if(1 == EVP_DecryptInit_ex(ctx, priv->cipher, NULL, priv->key, dec_ivec)) {
if(1 == EVP_CIPHER_CTX_set_padding(ctx, 0)) {
if(1 == EVP_DecryptUpdate(ctx, assembly, &evp_len, inbuf + TRANSOP_AES_PREAMBLE_SIZE, len)) {
evp_plaintext_len = evp_len;
if(1 == EVP_DecryptFinal_ex(ctx, assembly + evp_len, &evp_len)) {
evp_plaintext_len += evp_len;
if(evp_plaintext_len != len)
traceEvent(TRACE_ERROR, "decode_aes openssl decryption: decrypted %u bytes where %u were expected.\n",
evp_plaintext_len, len);
} else
traceEvent(TRACE_ERROR, "decode_aes openssl final decryption: %s\n", openssl_err_as_string());
} else
traceEvent(TRACE_ERROR, "decode_aes openssl decrpytion: %s\n", openssl_err_as_string());
} else
traceEvent(TRACE_ERROR, "decode_aes openssl padding setup: %s\n", openssl_err_as_string());
} else
traceEvent(TRACE_ERROR, "decode_aes openssl init: %s\n", openssl_err_as_string());
EVP_CIPHER_CTX_reset(ctx);
#else
AES_cbc_encrypt((inbuf + TRANSOP_AES_PREAMBLE_SIZE),
assembly, /* destination */
len,
&(priv->dec_key),
dec_ivec, AES_DECRYPT);
#endif
/* last byte is how much was padding: max value should be
* AES_BLOCKSIZE-1 */
padding = assembly[ len-1 ] & 0xff;
if(len >= padding) {
/* strictly speaking for this to be an ethernet packet
* it is going to need to be even bigger; but this is
* enough to prevent segfaults. */
traceEvent(TRACE_DEBUG, "padding = %u", padding);
len -= padding;
memcpy(outbuf,
assembly,
len);
} else
traceEvent(TRACE_WARNING, "UDP payload decryption failed.");
} else {
traceEvent(TRACE_WARNING, "Encrypted length %d is not a multiple of AES_BLOCK_SIZE (%d)", len, AES_BLOCK_SIZE);
len = 0;
}
} else
traceEvent(TRACE_ERROR, "decode_aes inbuf wrong size (%ul) to decrypt.", in_len);
uint8_t rest;
size_t penultimate_block;
uint8_t buf[AES_BLOCK_SIZE];
int len=-1;
if( ((in_len - AES_PREAMBLE_SIZE) <= N2N_PKT_BUF_SIZE) // cipher text fits in assembly
&& (in_len >= AES_PREAMBLE_SIZE) // has at least random number
&& (in_len >= AES_BLOCK_SIZE) // minimum size requirement for cipher text stealing
) {
traceEvent(TRACE_DEBUG, "transop_decode_aes %lu bytes ciphertext", in_len);
rest = in_len % AES_BLOCK_SIZE;
if(rest) {
// cipher text stealing
penultimate_block = ((in_len / AES_BLOCK_SIZE) - 1) * AES_BLOCK_SIZE;
// everything normal up to penultimate block
memcpy(assembly, inbuf, penultimate_block);
// prepare new penultimate block in buf
aes_ecb_decrypt(buf, inbuf + penultimate_block, priv->ctx);
memcpy(buf, inbuf + in_len - rest, rest);
// former penultimate block becomes new ultimate block
memcpy(assembly + penultimate_block + AES_BLOCK_SIZE, inbuf + penultimate_block, AES_BLOCK_SIZE);
// write new penultimate block from buf
memcpy(assembly + penultimate_block, buf, AES_BLOCK_SIZE);
// regular cbc decryption on the re-arranged ciphertext
aes_cbc_decrypt(assembly, assembly, in_len + AES_BLOCK_SIZE - rest, null_iv, priv->ctx);
// check for expected zero padding and give a warning otherwise
if (memcmp(assembly + in_len, null_iv, AES_BLOCK_SIZE - rest)) {
traceEvent(TRACE_WARNING, "transop_decode_aes payload decryption failed with unexpected cipher text stealing padding");
return -1;
}
} else {
// regular cbc decryption on multiple block-sized payload
aes_cbc_decrypt(assembly, inbuf, in_len, null_iv, priv->ctx);
}
len = in_len - AES_PREAMBLE_SIZE;
memcpy(outbuf,
assembly + AES_PREAMBLE_SIZE,
len);
} else
traceEvent(TRACE_ERROR, "transop_decode_aes inbuf wrong size (%ul) to decrypt", in_len);
return len;
}
/* ****************************************************** */
static int setup_aes_key(transop_aes_t *priv, const uint8_t *key, ssize_t key_size) {
size_t aes_key_size_bytes;
size_t aes_key_size_bits;
uint8_t key_mat_buf[SHA512_DIGEST_LENGTH + SHA256_DIGEST_LENGTH];
size_t key_mat_buf_length;
/* Clear out any old possibly longer key matter. */
#ifdef HAVE_OPENSSL_1_1
memset(&(priv->key), 0, sizeof(priv->key) );
#else
memset(&(priv->enc_key), 0, sizeof(priv->enc_key) );
memset(&(priv->dec_key), 0, sizeof(priv->dec_key) );
#endif
memset(&(priv->iv_enc_key), 0, sizeof(priv->iv_enc_key) );
memset(&(priv->iv_pad_val), 0, sizeof(priv->iv_pad_val) );
/* Let the user choose the degree of encryption:
* Long input keys will pick AES192 or AES256 with more robust but expensive encryption.
*
* The input key always gets hashed to make a more unpredictable use of the key space and
* also to derive some additional material (key for IV encrpytion, IV padding).
*
* The following scheme for key setup was discussed on github:
* https://github.com/ntop/n2n/issues/101
*/
/* create a working buffer of maximal occuring hashes size and generate
* the hashes for the aes key material, key_mat_buf_lengh indicates the
* actual "filling level" of the buffer
*/
if(key_size >= 65) {
#ifdef HAVE_OPENSSL_1_1
priv->cipher = EVP_aes_256_cbc();
#endif
aes_key_size_bytes = AES256_KEY_BYTES;
SHA512(key, key_size, key_mat_buf);
key_mat_buf_length = SHA512_DIGEST_LENGTH;
} else if(key_size >= 44) {
#ifdef HAVE_OPENSSL_1_1
priv->cipher = EVP_aes_192_cbc();
#endif
aes_key_size_bytes = AES192_KEY_BYTES;
SHA384(key, key_size, key_mat_buf);
/* append a hash of the first hash to create enough material for IV padding */
SHA256(key_mat_buf, SHA384_DIGEST_LENGTH, key_mat_buf + SHA384_DIGEST_LENGTH);
key_mat_buf_length = SHA384_DIGEST_LENGTH + SHA256_DIGEST_LENGTH;
} else {
#ifdef HAVE_OPENSSL_1_1
priv->cipher = EVP_aes_128_cbc();
#endif
aes_key_size_bytes = AES128_KEY_BYTES;
SHA256(key, key_size, key_mat_buf);
/* append a hash of the first hash to create enough material for IV padding */
SHA256(key_mat_buf, SHA256_DIGEST_LENGTH, key_mat_buf + SHA256_DIGEST_LENGTH);
key_mat_buf_length = 2 * SHA256_DIGEST_LENGTH;
}
/* is there enough material available? */
if(key_mat_buf_length < (aes_key_size_bytes + TRANSOP_AES_IV_KEY_BYTES + TRANSOP_AES_IV_PADDING_SIZE)) {
/* this should never happen */
traceEvent(TRACE_ERROR, "AES missing %u bits hashed key material\n",
(aes_key_size_bytes + TRANSOP_AES_IV_KEY_BYTES + TRANSOP_AES_IV_PADDING_SIZE - key_mat_buf_length) * 8);
return(1);
}
static int setup_aes_key(transop_aes_t *priv, const uint8_t *password, ssize_t password_len) {
/* setup of key, used for the CBC encryption */
aes_key_size_bits = 8 * aes_key_size_bytes;
unsigned char key_mat[32]; // maximum aes key length, equals hash length
unsigned char *key;
size_t key_size;
#ifdef HAVE_OPENSSL_1_1
memcpy (priv->key, key_mat_buf, aes_key_size_bytes);
#else
AES_set_encrypt_key(key_mat_buf, aes_key_size_bits, &(priv->enc_key));
AES_set_decrypt_key(key_mat_buf, aes_key_size_bits, &(priv->dec_key));
#endif
// let the user choose the degree of encryption:
// long input passwords will pick AES192 or AES256 with more robust but expensive encryption
/* setup of iv_enc_key (AES128 key) and iv_pad_val, used for generating the CBC IV */
AES_set_encrypt_key(key_mat_buf + aes_key_size_bytes, TRANSOP_AES_IV_KEY_BYTES * 8, &(priv->iv_enc_key));
memcpy(priv->iv_pad_val, key_mat_buf + aes_key_size_bytes + TRANSOP_AES_IV_KEY_BYTES, TRANSOP_AES_IV_PADDING_SIZE);
// the input password always gets hashed to make a more unpredictable use of the key space
// just think of usually reset MSB of ASCII coded password bytes
pearson_hash_256(key_mat, password, password_len);
traceEvent(TRACE_DEBUG, "AES %u bits setup completed\n",
aes_key_size_bits);
// the length-dependant scheme for key setup was discussed on github:
// https://github.com/ntop/n2n/issues/101
if(password_len >= 65) {
key_size = AES256_KEY_BYTES; // 256 bit
} else if(password_len >= 44) {
key_size = AES192_KEY_BYTES; // 192 bit
} else {
key_size = AES128_KEY_BYTES; // 128 bit
}
// and use the last key-sized part of the hash as aes key
key = key_mat + sizeof(key_mat) - key_size;
// setup the key and have corresponding context created
if (aes_init (key, key_size, &(priv->ctx))) {
traceEvent(TRACE_ERROR, "setup_aes_key %u-bit key setup unsuccessful",
key_size * 8);
return -1;
}
return(0);
traceEvent(TRACE_DEBUG, "setup_aes_key %u-bit key setup completed",
key_size * 8);
return 0;
}
/* ****************************************************** */
@ -426,7 +222,7 @@ static void transop_tick_aes(n2n_trans_op_t * arg, time_t now) { ; }
/* ****************************************************** */
/* AES initialization function */
// AES initialization function
int n2n_transop_aes_cbc_init(const n2n_edge_conf_t *conf, n2n_trans_op_t *ttt) {
transop_aes_t *priv;
const u_char *encrypt_key = (const u_char *)conf->encrypt_key;
@ -442,26 +238,13 @@ int n2n_transop_aes_cbc_init(const n2n_edge_conf_t *conf, n2n_trans_op_t *ttt) {
priv = (transop_aes_t*) calloc(1, sizeof(transop_aes_t));
if(!priv) {
traceEvent(TRACE_ERROR, "cannot allocate transop_aes_t memory");
traceEvent(TRACE_ERROR, "n2n_transop_aes_cbc_init cannot allocate transop_aes_t memory");
return(-1);
}
ttt->priv = priv;
#ifdef HAVE_OPENSSL_1_1
/* Setup openssl's reusable evp_* contexts for encryption and decryption*/
if(!(priv->enc_ctx = EVP_CIPHER_CTX_new())) {
traceEvent(TRACE_ERROR, "openssl's evp_* encryption context creation: %s\n", openssl_err_as_string());
return(-1);
}
if(!(priv->dec_ctx = EVP_CIPHER_CTX_new())) {
traceEvent(TRACE_ERROR, "openssl's evp_* decryption context creation: %s\n", openssl_err_as_string());
return(-1);
}
#endif
/* Setup the cipher and key */
// setup the cipher and key
return(setup_aes_key(priv, encrypt_key, encrypt_key_len));
}
#endif /* N2N_HAVE_AES */
#endif // N2N_HAVE_AES

22
src/wire.c

@ -103,6 +103,28 @@ int decode_uint32( uint32_t * out,
return 4;
}
int encode_uint64( uint8_t * base,
size_t * idx,
const uint64_t v )
{
*(uint64_t*)(base + *idx) = htobe64(v);
*idx += 8;
return 8;
}
int decode_uint64( uint64_t * out,
const uint8_t * base,
size_t * rem,
size_t * idx )
{
if (*rem < 8 ) { return 0; }
*out = be64toh(*(uint64_t*)base + *idx);
*idx += 8;
*rem -= 8;
return 8;
}
int encode_buf( uint8_t * base,
size_t * idx,
const void * p,

Loading…
Cancel
Save