[tarantool-patches] Re: [PATCH v3 3/4] crypto: implement crypto libary

Георгий Кириченко georgy at tarantool.org
Wed May 15 10:58:38 MSK 2019


LGTM
On Tuesday, May 7, 2019 11:53:58 PM MSK Vladislav Shpilevoy wrote:
> OpenSSL API is quite complex and hard to follow, additionally it
> is very unstable. Encoding/decoding via OpenSSL methods usually
> consists of multiple calls of a lot of functions. This patch
> wraps OpenSSL API with one more easy to use and conforming
> Tarantool code style in scope of crypto library.
> 
> The traditional OpenSSL API is wrapped as well in a form of
> crypto_stream object, so OpenSSL API is not cut off.
> 
> Besides struct crypto_stream the library provides struct
> crypto_codec which encapsulates all the steps of encryption logic
> in two short functions:
> 
>     crypto_codec_encrypt/decrypt(iv, in, in_size, out, out_size)
> 
> A caller can create a needed codec via crypto_codec_new, which
> now supports all the same algorithms as crypto.lua module.
> 
> Needed for #3234
> ---
>  src/lib/core/diag.h           |   2 +
>  src/lib/core/exception.cc     |  25 +++
>  src/lib/core/exception.h      |   7 +
>  src/lib/crypto/CMakeLists.txt |   2 +-
>  src/lib/crypto/crypto.c       | 287 ++++++++++++++++++++++++++++++++
>  src/lib/crypto/crypto.h       | 235 ++++++++++++++++++++++++++
>  test/unit/CMakeLists.txt      |   3 +
>  test/unit/crypto.c            | 302 ++++++++++++++++++++++++++++++++++
>  test/unit/crypto.result       | 132 +++++++++++++++
>  9 files changed, 994 insertions(+), 1 deletion(-)
>  create mode 100644 test/unit/crypto.c
>  create mode 100644 test/unit/crypto.result
> 
> diff --git a/src/lib/core/diag.h b/src/lib/core/diag.h
> index 78f0cdbdf..fd3831e66 100644
> --- a/src/lib/core/diag.h
> +++ b/src/lib/core/diag.h
> @@ -251,6 +251,8 @@ struct error *
>  BuildCollationError(const char *file, unsigned line, const char *format,
> ...); struct error *
>  BuildSwimError(const char *file, unsigned line, const char *format, ...);
> +struct error *
> +BuildCryptoError(const char *file, unsigned line, const char *format, ...);
> 
>  struct index_def;
> 
> diff --git a/src/lib/core/exception.cc b/src/lib/core/exception.cc
> index 6124c70d0..a6999af43 100644
> --- a/src/lib/core/exception.cc
> +++ b/src/lib/core/exception.cc
> @@ -280,6 +280,19 @@ SwimError::SwimError(const char *file, unsigned line,
> const char *format, ...) va_end(ap);
>  }
> 
> +const struct type_info type_CryptoError =
> +	make_type("CryptoError", &type_Exception);
> +
> +CryptoError::CryptoError(const char *file, unsigned line,
> +			 const char *format, ...)
> +	: Exception(&type_CryptoError, file, line)
> +{
> +	va_list ap;
> +	va_start(ap, format);
> +	error_vformat_msg(this, format, ap);
> +	va_end(ap);
> +}
> +
>  #define BuildAlloc(type)				\
>  	void *p = malloc(sizeof(type));			\
>  	if (p == NULL)					\
> @@ -371,6 +384,18 @@ BuildSwimError(const char *file, unsigned line, const
> char *format, ...) return e;
>  }
> 
> +struct error *
> +BuildCryptoError(const char *file, unsigned line, const char *format, ...)
> +{
> +	BuildAlloc(CryptoError);
> +	CryptoError *e =  new (p) CryptoError(file, line, "");
> +	va_list ap;
> +	va_start(ap, format);
> +	error_vformat_msg(e, format, ap);
> +	va_end(ap);
> +	return e;
> +}
> +
>  struct error *
>  BuildSocketError(const char *file, unsigned line, const char *socketname,
>  		 const char *format, ...)
> diff --git a/src/lib/core/exception.h b/src/lib/core/exception.h
> index 4f5b66f2e..a29281427 100644
> --- a/src/lib/core/exception.h
> +++ b/src/lib/core/exception.h
> @@ -51,6 +51,7 @@ extern const struct type_info type_IllegalParams;
>  extern const struct type_info type_SystemError;
>  extern const struct type_info type_CollationError;
>  extern const struct type_info type_SwimError;
> +extern const struct type_info type_CryptoError;
> 
>  const char *
>  exception_get_string(struct error *e, const struct method_info *method);
> @@ -166,6 +167,12 @@ public:
>  	virtual void raise() { throw this; }
>  };
> 
> +class CryptoError: public Exception {
> +public:
> +	CryptoError(const char *file, unsigned line, const char *format, ...);
> +	virtual void raise() { throw this; }
> +};
> +
>  /**
>   * Initialize the exception subsystem.
>   */
> diff --git a/src/lib/crypto/CMakeLists.txt b/src/lib/crypto/CMakeLists.txt
> index 7e2c6e1d3..4e2e5e403 100644
> --- a/src/lib/crypto/CMakeLists.txt
> +++ b/src/lib/crypto/CMakeLists.txt
> @@ -2,4 +2,4 @@ set(lib_sources crypto.c)
> 
>  set_source_files_compile_flags(${lib_sources})
>  add_library(crypto STATIC ${lib_sources})
> -target_link_libraries(crypto ${OPENSSL_LIBRARIES})
> +target_link_libraries(crypto ${OPENSSL_LIBRARIES} core)
> diff --git a/src/lib/crypto/crypto.c b/src/lib/crypto/crypto.c
> index 4b192ba2d..c1c8c88ea 100644
> --- a/src/lib/crypto/crypto.c
> +++ b/src/lib/crypto/crypto.c
> @@ -29,12 +29,299 @@
>   * SUCH DAMAGE.
>   */
>  #include "crypto.h"
> +#include "diag.h"
> +#include "exception.h"
> +#include "core/random.h"
>  #include <openssl/crypto.h>
>  #include <openssl/evp.h>
>  #include <openssl/err.h>
>  #include <openssl/ssl.h>
>  #include <openssl/hmac.h>
> 
> +const char *crypto_algo_strs[] = {
> +	"none",
> +	"AES128",
> +	"AES192",
> +	"AES256",
> +	"DES",
> +};
> +
> +const char *crypto_mode_strs[] = {
> +	"ECB",
> +	"CBC",
> +	"CFB",
> +	"OFB",
> +};
> +
> +/**
> + * Return a EVP_CIPHER object by a given algorithm name and a
> + * mode value. An algorithm name should be without quotes and with
> + * a format '<standard>[_<key_size>]', lowercase. For a list of
> + * supported algorithms see OpenSSL documentation.
> + */
> +#define algo_cipher_by_mode(algo, mode) ({		\
> +	const EVP_CIPHER *type;				\
> +	switch (mode) {					\
> +	case CRYPTO_MODE_ECB:				\
> +		type = EVP_##algo##_ecb();		\
> +		break;					\
> +	case CRYPTO_MODE_CBC:				\
> +		type = EVP_##algo##_cbc();		\
> +		break;					\
> +	case CRYPTO_MODE_CFB:				\
> +		type = EVP_##algo##_cfb();		\
> +		break;					\
> +	case CRYPTO_MODE_OFB:				\
> +		type = EVP_##algo##_ofb();		\
> +		break;					\
> +	default:					\
> +		type = NULL;				\
> +		diag_set(CryptoError, "unknown mode");	\
> +		break;					\
> +	}						\
> +	type;						\
> +})
> +
> +/**
> + * Find an OpenSSL cipher object by specified encryption
> + * algorithm and mode.
> + */
> +static inline const EVP_CIPHER *
> +evp_cipher_find(enum crypto_algo algo, enum crypto_mode mode)
> +{
> +	switch (algo) {
> +	case CRYPTO_ALGO_AES128:
> +		return algo_cipher_by_mode(aes_128, mode);
> +	case CRYPTO_ALGO_AES192:
> +		return algo_cipher_by_mode(aes_192, mode);
> +	case CRYPTO_ALGO_AES256:
> +		return algo_cipher_by_mode(aes_256, mode);
> +	case CRYPTO_ALGO_DES:
> +		return algo_cipher_by_mode(des, mode);
> +	case CRYPTO_ALGO_NONE:
> +		return EVP_enc_null();
> +	default:
> +		diag_set(CryptoError, "unknown crypto algorithm");
> +		return NULL;
> +	}
> +}
> +
> +/**
> + * Set a diag error with the latest OpenSSL error message. It is a
> + * macro so as to keep untouched line number in the error message.
> + */
> +#define diag_set_OpenSSL()					\
> +	diag_set(CryptoError, "OpenSSL error: %s",		\
> +		 ERR_error_string(ERR_get_error(), NULL))
> +
> +/** Stream to encrypt/decrypt data packets step by step. */
> +struct crypto_stream {
> +	/** Cipher type. Depends on algorithm and mode. */
> +	const EVP_CIPHER *cipher;
> +	/** Encryption/decryption context. */
> +	EVP_CIPHER_CTX *ctx;
> +	/** Stream direction. */
> +	enum crypto_direction dir;
> +};
> +
> +struct crypto_stream *
> +crypto_stream_new(enum crypto_algo algo, enum crypto_mode mode,
> +		  enum crypto_direction dir)
> +{
> +	const EVP_CIPHER *cipher = evp_cipher_find(algo, mode);
> +	if (cipher == NULL)
> +		return NULL;
> +	struct crypto_stream *s = (struct crypto_stream *) malloc(sizeof(*s));
> +	if (s == NULL) {
> +		diag_set(OutOfMemory, sizeof(*s), "malloc", "s");
> +		return NULL;
> +	}
> +	s->ctx = EVP_CIPHER_CTX_new();
> +	if (s->ctx == NULL) {
> +		free(s);
> +		diag_set_OpenSSL();
> +		return NULL;
> +	}
> +	s->cipher = cipher;
> +	s->dir = dir;
> +	return s;
> +}
> +
> +int
> +crypto_stream_begin(struct crypto_stream *s, const char *key, int key_size,
> +		    const char *iv, int iv_size)
> +{
> +	int need_size = EVP_CIPHER_key_length(s->cipher);
> +	if (key_size != need_size) {
> +		diag_set(CryptoError, "key size expected %d, got %d",
> +			 need_size, key_size);
> +		return -1;
> +	}
> +	need_size = EVP_CIPHER_iv_length(s->cipher);
> +	if (iv_size != need_size) {
> +		diag_set(CryptoError, "IV size expected %d, got %d",
> +			 need_size, iv_size);
> +		return -1;
> +	}
> +	if (EVP_CIPHER_CTX_cleanup(s->ctx) == 1 &&
> +	    EVP_CipherInit_ex(s->ctx, s->cipher, NULL,
> +			      (const unsigned char *) key,
> +			      (const unsigned char *) iv, s->dir) == 1)
> +		return 0;
> +	diag_set_OpenSSL();
> +	return -1;
> +}
> +
> +int
> +crypto_stream_append(struct crypto_stream *s, const char *in, int in_size,
> +		     char *out, int out_size)
> +{
> +	int len, need = in_size + EVP_CIPHER_block_size(s->cipher);
> +	if (need > out_size)
> +		return need;
> +	if (EVP_CipherUpdate(s->ctx, (unsigned char *) out, &len,
> +			     (const unsigned char *) in, in_size) == 1)
> +		return len;
> +	diag_set_OpenSSL();
> +	return -1;
> +}
> +
> +int
> +crypto_stream_commit(struct crypto_stream *s, char *out, int out_size)
> +{
> +	int need = EVP_CIPHER_block_size(s->cipher);
> +	if (need > out_size)
> +		return need;
> +	int len;
> +	if (EVP_CipherFinal_ex(s->ctx, (unsigned char *) out, &len) == 1 &&
> +	    EVP_CIPHER_CTX_cleanup(s->ctx) == 1)
> +		return len;
> +	diag_set_OpenSSL();
> +	return -1;
> +}
> +
> +void
> +crypto_stream_delete(struct crypto_stream *s)
> +{
> +	EVP_CIPHER_CTX_free(s->ctx);
> +	free(s);
> +}
> +
> +/**
> + * OpenSSL codec. Has a constant secret key, provides API to
> + * generate public keys, keeps OpenSSL contexts cached.
> + */
> +struct crypto_codec {
> +	/** Cipher type. Depends on algorithm and mode. */
> +	const EVP_CIPHER *cipher;
> +	/** Encryption context. */
> +	EVP_CIPHER_CTX *ctx;
> +	/**
> +	 * Secret key, usually unchanged among multiple data
> +	 * packets.
> +	 */
> +	unsigned char key[CRYPTO_MAX_KEY_SIZE];
> +};
> +
> +struct crypto_codec *
> +crypto_codec_new(enum crypto_algo algo, enum crypto_mode mode,
> +		 const char *key, int key_size)
> +{
> +	const EVP_CIPHER *cipher = evp_cipher_find(algo, mode);
> +	if (cipher == NULL)
> +		return NULL;
> +	int need = EVP_CIPHER_key_length(cipher);
> +	if (key_size != need) {
> +		diag_set(CryptoError, "key size expected %d, got %d",
> +			 need, key_size);
> +		return NULL;
> +	}
> +	struct crypto_codec *c = (struct crypto_codec *) malloc(sizeof(*c));
> +	if (c == NULL) {
> +		diag_set(OutOfMemory, sizeof(*c), "malloc", "c");
> +		return NULL;
> +	}
> +	c->ctx = EVP_CIPHER_CTX_new();
> +	if (c->ctx == NULL) {
> +		free(c);
> +		diag_set_OpenSSL();
> +		return NULL;
> +	}
> +	c->cipher = cipher;
> +	memcpy(c->key, key, key_size);
> +	return c;
> +}
> +
> +int
> +crypto_codec_gen_iv(struct crypto_codec *c, char *out, int out_size)
> +{
> +	int need = EVP_CIPHER_iv_length(c->cipher);
> +	if (out_size >= need)
> +		random_bytes(out, need);
> +	return need;
> +}
> +
> +int
> +crypto_codec_iv_size(const struct crypto_codec *c)
> +{
> +	return EVP_CIPHER_iv_length(c->cipher);
> +}
> +
> +/** Generic implementation of encrypt/decrypt methods. */
> +static int
> +crypto_codec_do_op(struct crypto_codec *c, const char *iv,
> +		   const char *in, int in_size, char *out, int out_size,
> +		   enum crypto_direction dir)
> +{
> +	const unsigned char *uin = (const unsigned char *) in;
> +	const unsigned char *uiv = (const unsigned char *) iv;
> +	unsigned char *uout = (unsigned char *) out;
> +	/*
> +	 * Note, that even if in_size is already multiple of block
> +	 * size, additional block is still needed. Result is
> +	 * almost always bigger than input. OpenSSL API advises to
> +	 * provide buffer of one block bigger than the data to
> +	 * encode.
> +	 */
> +	int need = in_size + EVP_CIPHER_block_size(c->cipher);
> +	if (need > out_size)
> +		return need;
> +
> +	int len1 = 0, len2 = 0;
> +	if (EVP_CipherInit_ex(c->ctx, c->cipher, NULL, c->key, uiv, dir) == 1 
&&
> +	    EVP_CipherUpdate(c->ctx, uout, &len1, uin, in_size) == 1 &&
> +	    EVP_CipherFinal_ex(c->ctx, uout + len1, &len2) == 1 &&
> +	    EVP_CIPHER_CTX_cleanup(c->ctx) == 1) {
> +		assert(len1 + len2 <= need);
> +		return len1 + len2;
> +	}
> +	diag_set_OpenSSL();
> +	return -1;
> +}
> +
> +int
> +crypto_codec_encrypt(struct crypto_codec *c, const char *iv,
> +		     const char *in, int in_size, char *out, int out_size)
> +{
> +	return crypto_codec_do_op(c, iv, in, in_size, out, out_size,
> +				  CRYPTO_DIR_ENCRYPT);
> +}
> +
> +int
> +crypto_codec_decrypt(struct crypto_codec *c, const char *iv,
> +		     const char *in, int in_size, char *out, int out_size)
> +{
> +	return crypto_codec_do_op(c, iv, in, in_size, out, out_size,
> +				  CRYPTO_DIR_DECRYPT);
> +}
> +
> +void
> +crypto_codec_delete(struct crypto_codec *c)
> +{
> +	EVP_CIPHER_CTX_free(c->ctx);
> +	free(c);
> +}
> +
>  void
>  crypto_init(void)
>  {
> diff --git a/src/lib/crypto/crypto.h b/src/lib/crypto/crypto.h
> index bc807e1a2..061d67135 100644
> --- a/src/lib/crypto/crypto.h
> +++ b/src/lib/crypto/crypto.h
> @@ -34,6 +34,241 @@
>  #if defined(__cplusplus)
>  extern "C" {
>  #endif
> +/**
> + * Cryptography library based on OpenSSL. It provides wrappers
> + * around OpenSSL functions encapsulating compatibility issues and
> + * problematic non-standard C API.
> + *
> + * Most of the cipher algorithms here are block-wise, with a
> + * secret key and sometimes with an additional public key. A
> + * secret key should be shared among communicating nodes and never
> + * transmitted explicitly.
> + *
> + * A public key in some algorithms is also called initial vector,
> + * and is an additional entropy factor. It prevents an attack when
> + * an attacker somehow learns a purpose or content of one data
> + * packet and immediately learns purpose of all the same looking
> + * packets. Initial vector should be generated randomly for each
> + * data packet, nonetheless it is not private and can be sent
> + * without encryption.
> + */
> +
> +enum crypto_algo {
> +	/** None to disable encryption. */
> +	CRYPTO_ALGO_NONE,
> +	/**
> +	 * AES - Advanced Encryption Standard. It is a symmetric
> +	 * key block cipher algorithm. AES encodes data in blocks
> +	 * of 128 bits, for what it uses a key and an initial
> +	 * vector. Key size can be 128, 192 and 256 bits. AES
> +	 * still is considered strong cipher.
> +	 */
> +	CRYPTO_ALGO_AES128,
> +	CRYPTO_ALGO_AES192,
> +	CRYPTO_ALGO_AES256,
> +	/**
> +	 * DES - Data Encryption Standard. It is a symmetric key
> +	 * block cipher algorithm. It has a fixed block size 64
> +	 * bits, a key of size 56 working bits and 8 not used for
> +	 * encryption. Its initial vector size is 64 bits as well.
> +	 * Note, that DES is not considered secure because of too
> +	 * small key size.
> +	 */
> +	CRYPTO_ALGO_DES,
> +	crypto_algo_MAX,
> +};
> +
> +extern const char *crypto_algo_strs[];
> +
> +enum crypto_mode {
> +	/**
> +	 * Electronic CodeBook. The message is split into blocks,
> +	 * and each block is encrypted separately. Weak, because
> +	 * data patterns leak. But fast, can be parallelized. And
> +	 * does not require an initial vector.
> +	 *
> +	 *  C_i = Encrypt(P_i)
> +	 */
> +	CRYPTO_MODE_ECB,
> +	/**
> +	 * Cipher Block Chaining. Each block of plain data is
> +	 * XORed with the previous encrypted block before being
> +	 * encrypted. The most commonly used mode. Encryption
> +	 * can't be parallelized because of sequential dependency
> +	 * of blocks, but decryption *can be*, because each block
> +	 * in fact depends only on single previous encrypted
> +	 * block. A decoder does not need to decrypt C_i-1 to
> +	 * decrypt C_i.
> +	 *
> +	 *  C_i = Encrypt(P_i ^ C_i-1).
> +	 *  C_0 = IV
> +	 */
> +	CRYPTO_MODE_CBC,
> +	/**
> +	 * Cipher FeedBack. Works similarly to CBC, but a bit
> +	 * different. Each block of plain data is encrypted as
> +	 * XOR of the plain data with second encryption of the
> +	 * previous block. This mode is able to encode a message
> +	 * byte by byte, without addition of a padding. Decryption
> +	 * can be parallelized.
> +	 *
> +	 *  C_i = Encrypt(C_i-1) ^ P_i
> +	 *  C_0 = IV
> +	 */
> +	CRYPTO_MODE_CFB,
> +	/**
> +	 * Output FeedBack. The same as CFB, but encryption can be
> +	 * parallelized partially.
> +	 *
> +	 * C_i = P_i ^ Encrypt(I_i)
> +	 * I_i = Encrypt(I_i-1)
> +	 * I_0 = IV
> +	 */
> +	CRYPTO_MODE_OFB,
> +	crypto_mode_MAX,
> +};
> +
> +extern const char *crypto_mode_strs[];
> +
> +/**
> + * Values obtained from EVP_CIPHER_*() API, but the constants are
> + * needed in some places at compilation time. For example, for
> + * statically sized buffers.
> + */
> +enum {
> +	CRYPTO_AES_BLOCK_SIZE = 16,
> +	CRYPTO_AES_IV_SIZE = 16,
> +	CRYPTO_AES128_KEY_SIZE = 16,
> +	CRYPTO_AES192_KEY_SIZE = 24,
> +	CRYPTO_AES256_KEY_SIZE = 32,
> +
> +	CRYPTO_DES_BLOCK_SIZE = 8,
> +	CRYPTO_DES_IV_SIZE = 8,
> +	CRYPTO_DES_KEY_SIZE = 8,
> +
> +	CRYPTO_MAX_KEY_SIZE = 32,
> +	CRYPTO_MAX_IV_SIZE = 16,
> +};
> +
> +/**
> + * OpenSSL API provides generic methods to do both encryption and
> + * decryption depending on one 'int' parameter passed into
> + * initialization function EVP_CipherInit_ex. Here these constants
> + * are assigned to more readable names.
> + */
> +enum crypto_direction {
> +	CRYPTO_DIR_DECRYPT = 0,
> +	CRYPTO_DIR_ENCRYPT = 1,
> +};
> +
> +struct crypto_stream;
> +
> +/**
> + * Crypto stream is an object allowing to encrypt/decrypt data
> + * packets with different public and secret keys step by step.
> + */
> +struct crypto_stream *
> +crypto_stream_new(enum crypto_algo algo, enum crypto_mode mode,
> +		  enum crypto_direction dir);
> +
> +/**
> + * Start a new data packet. @a key and @a iv are secret and public
> + * keys respectively.
> + * @retval 0 Success.
> + * @retval -1 Error. Diag is set.
> + */
> +int
> +crypto_stream_begin(struct crypto_stream *s, const char *key, int key_size,
> +		    const char *iv, int iv_size);
> +
> +/**
> + * Encrypt/decrypt next part of the current data packet.
> + * @retval -1 Error. Diag is set.
> + * @return Number of written bytes. If > @a out_size then nothing
> + *         is written, needed number of bytes is returned.
> + */
> +int
> +crypto_stream_append(struct crypto_stream *s, const char *in, int in_size,
> +		     char *out, int out_size);
> +
> +/**
> + * Finalize the current data packet. Note, a margin can be added
> + * to the result.
> + * @retval -1 Error. Diag is set.
> + * @return Number of written bytes. If > @a out_size then nothing
> + *         is written, needed number of bytes is returned.
> + */
> +int
> +crypto_stream_commit(struct crypto_stream *s, char *out, int out_size);
> +
> +/** Delete a stream, free its memory. */
> +void
> +crypto_stream_delete(struct crypto_stream *s);
> +
> +struct crypto_codec;
> +
> +/**
> + * Create a new codec working with a specified @a algo algorithm
> + * in @a mode. It is remarkable that both algorithm and mode
> + * strongly affect secrecy and efficiency. Codec is similar to
> + * stream, but provides shorter and simpler API, and can both
> + * decrypt and encrypt without recreation.
> + */
> +struct crypto_codec *
> +crypto_codec_new(enum crypto_algo algo, enum crypto_mode mode,
> +		 const char *key, int key_size);
> +
> +/**
> + * Generate a new initial vector, dump it into @a out buffer and
> + * return number of written (or wanted to be written) bytes.
> + */
> +int
> +crypto_codec_gen_iv(struct crypto_codec *c, char *out, int out_size);
> +
> +/**
> + * Initial vector size. It is a constant depending on an
> + * algorithm and mode.
> + */
> +int
> +crypto_codec_iv_size(const struct crypto_codec *c);
> +
> +/**
> + * Encrypt plain data by codec @a c.
> + * @param c Codec.
> + * @param iv Initial vector.
> + * @param in Plain input data.
> + * @param in_size Byte size of @a in.
> + * @param out Output buffer.
> + * @param out_size Byte size of @a out.
> + *
> + * @retval -1 Error. Diag is set.
> + * @return Number of written bytes. If > @a out_size then nothing
> + *         is written, needed number of bytes is returned.
> + */
> +int
> +crypto_codec_encrypt(struct crypto_codec *c, const char *iv,
> +		     const char *in, int in_size, char *out, int out_size);
> +
> +/**
> + * Decrypt cipher by codec @a c.
> + * @param c Codec.
> + * @param iv Initial vector used to encode @a in.
> + * @param in Cipher to decode.
> + * @param in_size Byte size of @a in.
> + * @param out Output buffer.
> + * @param out_size Byte size of @a out.
> + *
> + * @retval -1 Error. Diag is set.
> + * @return Number of written bytes. If > @a out_size then nothing
> + *         is written, needed number of bytes is returned.
> + */
> +int
> +crypto_codec_decrypt(struct crypto_codec *c, const char *iv,
> +		     const char *in, int in_size, char *out, int out_size);
> +
> +/** Delete a codec, free its memory. */
> +void
> +crypto_codec_delete(struct crypto_codec *c);
> 
>  void
>  crypto_init(void);
> diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt
> index 2c8340800..d3cced140 100644
> --- a/test/unit/CMakeLists.txt
> +++ b/test/unit/CMakeLists.txt
> @@ -217,6 +217,9 @@ target_link_libraries(checkpoint_schedule.test m unit)
>  add_executable(sio.test sio.c)
>  target_link_libraries(sio.test unit core)
> 
> +add_executable(crypto.test crypto.c)
> +target_link_libraries(crypto.test crypto unit)
> +
>  add_executable(swim.test swim.c swim_test_transport.c swim_test_ev.c
>                 swim_test_utils.c ${PROJECT_SOURCE_DIR}/src/version.c)
>  target_link_libraries(swim.test unit swim)
> diff --git a/test/unit/crypto.c b/test/unit/crypto.c
> new file mode 100644
> index 000000000..fad1842f1
> --- /dev/null
> +++ b/test/unit/crypto.c
> @@ -0,0 +1,302 @@
> +/*
> + * Copyright 2010-2019, Tarantool AUTHORS, please see AUTHORS file.
> + *
> + * Redistribution and use in source and binary forms, with or
> + * without modification, are permitted provided that the following
> + * conditions are met:
> + *
> + * 1. Redistributions of source code must retain the above
> + *    copyright notice, this list of conditions and the
> + *    following disclaimer.
> + *
> + * 2. Redistributions in binary form must reproduce the above
> + *    copyright notice, this list of conditions and the following
> + *    disclaimer in the documentation and/or other materials
> + *    provided with the distribution.
> + *
> + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
> + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
> + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
> + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
> + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
> + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
> + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
> + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
> + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
> + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
> + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
> + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
> + * SUCH DAMAGE.
> + */
> +#include "crypto/crypto.h"
> +#include "core/random.h"
> +#include "unit.h"
> +#include "trivia/util.h"
> +#include "memory.h"
> +#include "fiber.h"
> +
> +static void
> +test_aes128_codec(void)
> +{
> +	header();
> +	plan(20);
> +
> +	char key[CRYPTO_AES128_KEY_SIZE];
> +	char iv[CRYPTO_AES_IV_SIZE], iv2[CRYPTO_AES_IV_SIZE];
> +	random_bytes(key, sizeof(key));
> +	struct crypto_codec *c =
> +		crypto_codec_new(CRYPTO_ALGO_AES128, CRYPTO_MODE_CBC,
> +				 key, sizeof(key));
> +
> +	int rc = crypto_codec_encrypt(c, NULL, NULL, 10, NULL, 0);
> +	is(rc, 26, "encrypt returns needed number of bytes");
> +	rc = crypto_codec_encrypt(c, NULL, NULL, 10, NULL, 15);
> +	is(rc, 26, "encrypt does not write anything when too small "\
> +	   "buffer");
> +	rc = crypto_codec_encrypt(c, NULL, NULL, 0, NULL, 0);
> +	is(rc, 16, "encrypt does not allow 0 sized buffer");
> +	rc = crypto_codec_encrypt(c, NULL, NULL, 32, NULL, 0);
> +	is(rc, 48, "encrypt requires additional block when buffer "\
> +	   "size is multiple of block size");
> +
> +	const char *plain = "plain text";
> +	int plain_size = strlen(plain) + 1;
> +	char buffer1[128], buffer2[128];
> +	memset(buffer1, 0, sizeof(buffer1));
> +	memset(buffer2, 0, sizeof(buffer2));
> +	int buffer_size = sizeof(buffer1);
> +	int iv_size = crypto_codec_gen_iv(c, iv, sizeof(iv));
> +	is(iv_size, CRYPTO_AES_IV_SIZE, "AES 126 IV size is %d",
> +	   CRYPTO_AES_IV_SIZE);
> +
> +	rc = crypto_codec_encrypt(c, iv, plain, plain_size,
> +				  buffer1, buffer_size);
> +	is(rc, 16, "encrypt works when buffer is big enough");
> +	rc = crypto_codec_encrypt(c, iv, plain, plain_size,
> +				  buffer2, buffer_size);
> +	is(rc, 16, "encrypt returns the same on second call");
> +	is(memcmp(buffer1, buffer2, rc), 0, "encrypted data is the same");
> +	isnt(memcmp(buffer1, plain, plain_size), 0,
> +	     "and it is not just copied from the plain text");
> +
> +	rc = crypto_codec_decrypt(c, iv, NULL, 16, NULL, 0);
> +	is(rc, 32, "decrypt also checks length and returns needed number "\
> +	   "of bytes");
> +	rc = crypto_codec_decrypt(c, iv, buffer1, 16, buffer2, buffer_size);
> +	is(rc, plain_size, "decrypt returns correct number of bytes");
> +	is(memcmp(buffer2, plain, plain_size), 0,
> +	   "and correctly decrypts data");
> +
> +	rc = crypto_codec_decrypt(c, "false iv not meaning anything",
> +				  buffer1, 16, buffer2, buffer_size);
> +	is(rc, -1, "decrypt can fail with wrong IV");
> +	ok(! diag_is_empty(diag_get()), "diag error is set");
> +
> +	crypto_codec_gen_iv(c, iv2, sizeof(iv2));
> +	rc = crypto_codec_encrypt(c, iv2, plain, plain_size,
> +				  buffer2, buffer_size);
> +	is(rc, 16, "encrypt with different IV and the same number of written "\
> +	   "bytes returned")
> +	isnt(memcmp(buffer2, buffer1, rc), 0,
> +	     "the encrypted data looks different");
> +	rc = crypto_codec_decrypt(c, iv2, buffer2, 16, buffer1, buffer_size);
> +	is(rc, plain_size, "decrypt works with correct but another IV");
> +	is(memcmp(buffer1, plain, plain_size), 0, "data is the same");
> +
> +	struct crypto_codec *c2 =
> +		crypto_codec_new(CRYPTO_ALGO_AES128, CRYPTO_MODE_CBC,
> +				 key, sizeof(key));
> +	rc = crypto_codec_encrypt(c, iv2, plain, plain_size,
> +				  buffer1, buffer_size);
> +	memset(buffer2, 0, rc);
> +	rc = crypto_codec_decrypt(c2, iv2, buffer1, rc, buffer2, buffer_size);
> +	is(rc, plain_size, "encrypt with one codec, but decrypt with another 
"\
> +	   "codec and the same key");
> +	is(memcmp(plain, buffer2, plain_size), 0, "data is the same");
> +
> +	crypto_codec_delete(c2);
> +	crypto_codec_delete(c);
> +
> +	check_plan();
> +	footer();
> +}
> +
> +static void
> +test_aes128_stress(void)
> +{
> +	header();
> +	plan(1);
> +	char key[CRYPTO_AES128_KEY_SIZE], iv[CRYPTO_AES_IV_SIZE];
> +	random_bytes(key, sizeof(key));
> +	struct crypto_codec *c =
> +		crypto_codec_new(CRYPTO_ALGO_AES128, CRYPTO_MODE_CBC,
> +				 key, sizeof(key));
> +
> +	char plain[515], cipher[1024], result[1024];
> +	int rc, iv_size, size = 10;
> +	for (int size = 10; size < (int) sizeof(plain); size += 10) {
> +		random_bytes(plain, size);
> +		rc = crypto_codec_gen_iv(c, iv, sizeof(iv));
> +		fail_if(rc != sizeof(iv));
> +		rc = crypto_codec_encrypt(c, iv, plain, size,
> +					  cipher, sizeof(cipher));
> +		rc = crypto_codec_decrypt(c, iv, cipher, rc,
> +					  result, sizeof(result));
> +		fail_if(memcmp(result, plain, rc) != 0);
> +	}
> +	ok(true, "try encrypt/decrypt on a variety of sizes, keys, and ivs");
> +
> +	check_plan();
> +	crypto_codec_delete(c);
> +	footer();
> +}
> +
> +static void
> +test_algo_mode_key(enum crypto_algo algo, enum crypto_mode mode, int
> key_size) +{
> +	char key[CRYPTO_MAX_KEY_SIZE], buffer1[128], buffer2[128], plain[128];
> +	char iv[CRYPTO_MAX_IV_SIZE];
> +	int plain_size = rand() % 100;
> +	random_bytes(plain, plain_size);
> +	random_bytes(key, key_size);
> +	int buffer_size = sizeof(buffer1);
> +	struct crypto_codec *c = crypto_codec_new(algo, mode, key, key_size);
> +	int iv_size = crypto_codec_gen_iv(c, iv, sizeof(iv));
> +	is(iv_size, crypto_codec_iv_size(c), "%s %d %s, create iv of size %d",
> +	   crypto_algo_strs[algo], key_size, crypto_mode_strs[mode], iv_size);
> +	int encoded = crypto_codec_encrypt(c, iv, plain, plain_size,
> +					   buffer1, buffer_size);
> +	ok(encoded >= 0, "encode");
> +	int decoded = crypto_codec_decrypt(c, iv, buffer1, encoded,
> +					   buffer2, buffer_size);
> +	is(decoded, plain_size, "decode");
> +	is(memcmp(plain, buffer2, plain_size), 0, "data is correct");
> +	crypto_codec_delete(c);
> +}
> +
> +static inline void
> +test_algo_key(enum crypto_algo algo, int key_size)
> +{
> +	for (enum crypto_mode mode = 0; mode < crypto_mode_MAX; ++mode)
> +		test_algo_mode_key(algo, mode, key_size);
> +}
> +
> +static void
> +test_each(void)
> +{
> +	header();
> +	plan(80);
> +
> +	test_algo_key(CRYPTO_ALGO_NONE, 0);
> +	test_algo_key(CRYPTO_ALGO_AES128, CRYPTO_AES128_KEY_SIZE);
> +	test_algo_key(CRYPTO_ALGO_AES192, CRYPTO_AES192_KEY_SIZE);
> +	test_algo_key(CRYPTO_ALGO_AES256, CRYPTO_AES256_KEY_SIZE);
> +	test_algo_key(CRYPTO_ALGO_DES, CRYPTO_DES_KEY_SIZE);
> +
> +	check_plan();
> +	footer();
> +}
> +
> +static void
> +test_stream(void)
> +{
> +	header();
> +	plan(11);
> +
> +	char key[CRYPTO_AES128_KEY_SIZE], iv[CRYPTO_AES_IV_SIZE];
> +	char buffer1[128], buffer2[128];
> +	random_bytes(key, sizeof(key));
> +	random_bytes(iv, sizeof(iv));
> +	struct crypto_stream *encoder =
> +		crypto_stream_new(CRYPTO_ALGO_AES128, CRYPTO_MODE_CBC,
> +				  CRYPTO_DIR_ENCRYPT);
> +	is(crypto_stream_begin(encoder, key, 3, iv, sizeof(iv)), -1,
> +	   "stream begin checks key size");
> +	is(crypto_stream_begin(encoder, key, sizeof(key), iv, 3), -1,
> +	   "stream begin checks iv size");
> +	is(crypto_stream_begin(encoder, key, sizeof(key), iv, sizeof(iv)), 0,
> +	   "begin encryption");
> +	const char *plain = "long long long long long long long plain text";
> +	int plain_size = strlen(plain);
> +
> +	const char *in = plain;
> +	int in_size = plain_size;
> +	char *out = buffer1;
> +	int out_size = sizeof(buffer1);
> +	int encoded = crypto_stream_append(encoder, in, in_size, NULL, 0);
> +	is(encoded, in_size + CRYPTO_AES_BLOCK_SIZE, "append checks size");
> +	int chunk_size = 5;
> +	int rc = crypto_stream_append(encoder, in, chunk_size, out, out_size);
> +	ok(rc >= 0, "append %d", chunk_size);
> +	in += chunk_size;
> +	in_size -= chunk_size;
> +	out += rc;
> +	out_size -= rc;
> +	encoded = rc;
> +
> +	chunk_size = 10;
> +	rc = crypto_stream_append(encoder, in, chunk_size, out, out_size);
> +	ok(rc >= 0, "append %d", chunk_size);
> +	in += chunk_size;
> +	in_size -= chunk_size;
> +	out += rc;
> +	out_size -= rc;
> +	encoded += rc;
> +
> +	rc = crypto_stream_append(encoder, in, in_size, out, out_size);
> +	ok(rc >= 0, "last append %d", in_size);
> +	out += rc;
> +	out_size -= rc;
> +	encoded += rc;
> +
> +	rc = crypto_stream_commit(encoder, NULL, 0);
> +	is(rc, CRYPTO_AES_BLOCK_SIZE, "commit checks size");
> +	rc = crypto_stream_commit(encoder, out, out_size);
> +	ok(rc >= 0, "commit %d", rc);
> +	out += rc;
> +	encoded += rc;
> +
> +	struct crypto_stream *decoder =
> +		crypto_stream_new(CRYPTO_ALGO_AES128, CRYPTO_MODE_CBC,
> +				  CRYPTO_DIR_DECRYPT);
> +	crypto_stream_begin(decoder, key, sizeof(key), iv, sizeof(iv));
> +	int decoded = crypto_stream_append(decoder, buffer1, encoded,
> +					   buffer2, sizeof(buffer2));
> +	decoded += crypto_stream_commit(decoder, buffer2 + decoded,
> +					sizeof(buffer2) - decoded);
> +	is(decoded, plain_size, "decoder returned correct size");
> +	is(memcmp(plain, buffer2, plain_size), 0, "data is decoded correctly");
> +
> +	crypto_stream_delete(encoder);
> +	crypto_stream_delete(decoder);
> +
> +	check_plan();
> +	footer();
> +}
> +
> +int
> +main(void)
> +{
> +	header();
> +	plan(5);
> +	random_init();
> +	crypto_init();
> +	memory_init();
> +	fiber_init(fiber_c_invoke);
> +
> +	struct crypto_codec *c = crypto_codec_new(-1, -1, "1234", 4);
> +	is(c, NULL, "crypto checks that algo argument is correct");
> +
> +	test_aes128_codec();
> +	test_aes128_stress();
> +	test_each();
> +	test_stream();
> +
> +	fiber_free();
> +	memory_free();
> +	crypto_free();
> +	random_free();
> +	int rc = check_plan();
> +	footer();
> +	return rc;
> +}
> diff --git a/test/unit/crypto.result b/test/unit/crypto.result
> new file mode 100644
> index 000000000..d6ff327e6
> --- /dev/null
> +++ b/test/unit/crypto.result
> @@ -0,0 +1,132 @@
> +	*** main ***
> +1..5
> +ok 1 - crypto checks that algo argument is correct
> +	*** test_aes128_codec ***
> +    1..20
> +    ok 1 - encrypt returns needed number of bytes
> +    ok 2 - encrypt does not write anything when too small buffer
> +    ok 3 - encrypt does not allow 0 sized buffer
> +    ok 4 - encrypt requires additional block when buffer size is multiple
> of block size +    ok 5 - AES 126 IV size is 16
> +    ok 6 - encrypt works when buffer is big enough
> +    ok 7 - encrypt returns the same on second call
> +    ok 8 - encrypted data is the same
> +    ok 9 - and it is not just copied from the plain text
> +    ok 10 - decrypt also checks length and returns needed number of bytes
> +    ok 11 - decrypt returns correct number of bytes
> +    ok 12 - and correctly decrypts data
> +    ok 13 - decrypt can fail with wrong IV
> +    ok 14 - diag error is set
> +    ok 15 - encrypt with different IV and the same number of written bytes
> returned +    ok 16 - the encrypted data looks different
> +    ok 17 - decrypt works with correct but another IV
> +    ok 18 - data is the same
> +    ok 19 - encrypt with one codec, but decrypt with another codec and the
> same key +    ok 20 - data is the same
> +ok 2 - subtests
> +	*** test_aes128_codec: done ***
> +	*** test_aes128_stress ***
> +    1..1
> +    ok 1 - try encrypt/decrypt on a variety of sizes, keys, and ivs
> +ok 3 - subtests
> +	*** test_aes128_stress: done ***
> +	*** test_each ***
> +    1..80
> +    ok 1 - none 0 ECB, create iv of size 0
> +    ok 2 - encode
> +    ok 3 - decode
> +    ok 4 - data is correct
> +    ok 5 - none 0 CBC, create iv of size 0
> +    ok 6 - encode
> +    ok 7 - decode
> +    ok 8 - data is correct
> +    ok 9 - none 0 CFB, create iv of size 0
> +    ok 10 - encode
> +    ok 11 - decode
> +    ok 12 - data is correct
> +    ok 13 - none 0 OFB, create iv of size 0
> +    ok 14 - encode
> +    ok 15 - decode
> +    ok 16 - data is correct
> +    ok 17 - AES128 16 ECB, create iv of size 0
> +    ok 18 - encode
> +    ok 19 - decode
> +    ok 20 - data is correct
> +    ok 21 - AES128 16 CBC, create iv of size 16
> +    ok 22 - encode
> +    ok 23 - decode
> +    ok 24 - data is correct
> +    ok 25 - AES128 16 CFB, create iv of size 16
> +    ok 26 - encode
> +    ok 27 - decode
> +    ok 28 - data is correct
> +    ok 29 - AES128 16 OFB, create iv of size 16
> +    ok 30 - encode
> +    ok 31 - decode
> +    ok 32 - data is correct
> +    ok 33 - AES192 24 ECB, create iv of size 0
> +    ok 34 - encode
> +    ok 35 - decode
> +    ok 36 - data is correct
> +    ok 37 - AES192 24 CBC, create iv of size 16
> +    ok 38 - encode
> +    ok 39 - decode
> +    ok 40 - data is correct
> +    ok 41 - AES192 24 CFB, create iv of size 16
> +    ok 42 - encode
> +    ok 43 - decode
> +    ok 44 - data is correct
> +    ok 45 - AES192 24 OFB, create iv of size 16
> +    ok 46 - encode
> +    ok 47 - decode
> +    ok 48 - data is correct
> +    ok 49 - AES256 32 ECB, create iv of size 0
> +    ok 50 - encode
> +    ok 51 - decode
> +    ok 52 - data is correct
> +    ok 53 - AES256 32 CBC, create iv of size 16
> +    ok 54 - encode
> +    ok 55 - decode
> +    ok 56 - data is correct
> +    ok 57 - AES256 32 CFB, create iv of size 16
> +    ok 58 - encode
> +    ok 59 - decode
> +    ok 60 - data is correct
> +    ok 61 - AES256 32 OFB, create iv of size 16
> +    ok 62 - encode
> +    ok 63 - decode
> +    ok 64 - data is correct
> +    ok 65 - DES 8 ECB, create iv of size 0
> +    ok 66 - encode
> +    ok 67 - decode
> +    ok 68 - data is correct
> +    ok 69 - DES 8 CBC, create iv of size 8
> +    ok 70 - encode
> +    ok 71 - decode
> +    ok 72 - data is correct
> +    ok 73 - DES 8 CFB, create iv of size 8
> +    ok 74 - encode
> +    ok 75 - decode
> +    ok 76 - data is correct
> +    ok 77 - DES 8 OFB, create iv of size 8
> +    ok 78 - encode
> +    ok 79 - decode
> +    ok 80 - data is correct
> +ok 4 - subtests
> +	*** test_each: done ***
> +	*** test_stream ***
> +    1..11
> +    ok 1 - stream begin checks key size
> +    ok 2 - stream begin checks iv size
> +    ok 3 - begin encryption
> +    ok 4 - append checks size
> +    ok 5 - append 5
> +    ok 6 - append 10
> +    ok 7 - last append 30
> +    ok 8 - commit checks size
> +    ok 9 - commit 16
> +    ok 10 - decoder returned correct size
> +    ok 11 - data is decoded correctly
> +ok 5 - subtests
> +	*** test_stream: done ***
> +	*** main: done ***

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 488 bytes
Desc: This is a digitally signed message part.
URL: <https://lists.tarantool.org/pipermail/tarantool-patches/attachments/20190515/d6efab0b/attachment.sig>


More information about the Tarantool-patches mailing list