From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from localhost (localhost [127.0.0.1]) by turing.freelists.org (Avenir Technologies Mail Multiplex) with ESMTP id C26342E6C1 for ; Wed, 15 May 2019 03:58:40 -0400 (EDT) Received: from turing.freelists.org ([127.0.0.1]) by localhost (turing.freelists.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id 1VT0d5q6Y5Ed for ; Wed, 15 May 2019 03:58:40 -0400 (EDT) Received: from smtp49.i.mail.ru (smtp49.i.mail.ru [94.100.177.109]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by turing.freelists.org (Avenir Technologies Mail Multiplex) with ESMTPS id 34DD82E69C for ; Wed, 15 May 2019 03:58:40 -0400 (EDT) Received: by smtp49.i.mail.ru with esmtpa (envelope-from ) id 1hQooA-0003fq-Pc for tarantool-patches@freelists.org; Wed, 15 May 2019 10:58:39 +0300 From: =?utf-8?B?0JPQtdC+0YDQs9C40Lkg0JrQuNGA0LjRh9C10L3QutC+?= Subject: [tarantool-patches] Re: [PATCH v3 3/4] crypto: implement crypto libary Date: Wed, 15 May 2019 10:58:38 +0300 Message-ID: <2744941.Qik98TSR1z@home.lan> In-Reply-To: References: MIME-Version: 1.0 Content-Type: multipart/signed; boundary="nextPart6654717.HRVMtrid0K"; micalg="pgp-sha256"; protocol="application/pgp-signature" Sender: tarantool-patches-bounce@freelists.org Errors-to: tarantool-patches-bounce@freelists.org Reply-To: tarantool-patches@freelists.org List-Help: List-Unsubscribe: List-software: Ecartis version 1.0.0 List-Id: tarantool-patches List-Subscribe: List-Owner: List-post: List-Archive: To: tarantool-patches@freelists.org --nextPart6654717.HRVMtrid0K Content-Transfer-Encoding: 7Bit Content-Type: text/plain; charset="us-ascii" 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 > #include > #include > #include > #include > > +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 '[_]', 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 ``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 > + * 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 *** --nextPart6654717.HRVMtrid0K Content-Type: application/pgp-signature; name="signature.asc" Content-Description: This is a digitally signed message part. Content-Transfer-Encoding: 7Bit -----BEGIN PGP SIGNATURE----- iQEzBAABCAAdFiEEFB+nbqWGnp59Rk9ZFSyY70x8X3sFAlzbxq4ACgkQFSyY70x8 X3sGuQgAqun2+JKL2t9idwvAE7I0M8L+75hfPWHxltXphcUV8JQx5uH7OPtToeJq 2bO4XgURkgrGMcNaJWdWkTRMlkUNRkxbWLb6Ne9AKQY3F/why2B2q58Df2KB+94V ru0XJ0IGWF1uYLjDyuRZEMHhbiqlx5asm4xZjE2LJHSkNpY4SxQ6DPUrs5vDCOSx v9Vzyhh2Xb+iafO8jvDdrs8b40WMVJyQtWHpwrB+hrWdv+Dw5PEd43ruDnuHAdg1 LDwfmxAfGscWpSWdJ0j2w/MRudH7ibY9cBxvHUlaHCvd480t2ps5tk539FYevLxE x58RJ/aPMAGCVCiYycFzHQfdnWfn0Q== =d1Zi -----END PGP SIGNATURE----- --nextPart6654717.HRVMtrid0K--