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

Vladislav Shpilevoy v.shpilevoy at tarantool.org
Tue May 7 23:53:58 MSK 2019


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 ***
-- 
2.20.1 (Apple Git-117)






More information about the Tarantool-patches mailing list