Tarantool development patches archive
 help / color / mirror / Atom feed
From: Vladislav Shpilevoy <v.shpilevoy@tarantool.org>
To: tarantool-patches@freelists.org
Cc: kostja@tarantool.org, georgy@tarantool.org
Subject: [tarantool-patches] [PATCH v3 4/4] crypto: use crypto library in crypto.lua
Date: Tue,  7 May 2019 23:53:59 +0300	[thread overview]
Message-ID: <470c7d23d93014f4c03bb7f1085a33cc8e249097.1557262174.git.v.shpilevoy@tarantool.org> (raw)
In-Reply-To: <cover.1557262174.git.v.shpilevoy@tarantool.org>

crypto.lua is a public module using OpenSSL directly. But now
lib/crypto encapsulates OpenSSL with additional checks and
similar but more conforming API. It allows to replace OpenSSL
cipher in crypto.lua with lib/crypto methods.
---
 extra/exports            |   7 +-
 src/lib/crypto/crypto.c  |  12 --
 src/lua/crypto.lua       | 241 +++++++++++++++++++++------------------
 test/app/crypto.result   | 114 +++++++++++++-----
 test/app/crypto.test.lua |  32 +++++-
 5 files changed, 249 insertions(+), 157 deletions(-)

diff --git a/extra/exports b/extra/exports
index 562f8a164..269672fa4 100644
--- a/extra/exports
+++ b/extra/exports
@@ -77,12 +77,15 @@ space_run_triggers
 space_bsize
 box_schema_version
 
-crypto_EVP_CIPHER_key_length
-crypto_EVP_CIPHER_iv_length
 crypto_EVP_MD_CTX_new
 crypto_EVP_MD_CTX_free
 crypto_HMAC_CTX_new
 crypto_HMAC_CTX_free
+crypto_stream_new
+crypto_stream_begin
+crypto_stream_append
+crypto_stream_commit
+crypto_stream_delete
 
 # Module API
 
diff --git a/src/lib/crypto/crypto.c b/src/lib/crypto/crypto.c
index c1c8c88ea..9b9d89b0b 100644
--- a/src/lib/crypto/crypto.c
+++ b/src/lib/crypto/crypto.c
@@ -343,18 +343,6 @@ crypto_free(void)
 #endif
 }
 
-int
-crypto_EVP_CIPHER_key_length(const EVP_CIPHER *cipher)
-{
-	return EVP_CIPHER_key_length(cipher);
-}
-
-int
-crypto_EVP_CIPHER_iv_length(const EVP_CIPHER *cipher)
-{
-	return EVP_CIPHER_iv_length(cipher);
-}
-
 EVP_MD_CTX *
 crypto_EVP_MD_CTX_new(void)
 {
diff --git a/src/lua/crypto.lua b/src/lua/crypto.lua
index 30504b1de..88f562b45 100644
--- a/src/lua/crypto.lua
+++ b/src/lua/crypto.lua
@@ -28,23 +28,45 @@ ffi.cdef[[
     int HMAC_Update(HMAC_CTX *ctx, const unsigned char *data, size_t len);
     int HMAC_Final(HMAC_CTX *ctx, unsigned char *md, unsigned int *len);
 
-    typedef struct {} EVP_CIPHER_CTX;
-    typedef struct {} EVP_CIPHER;
-    EVP_CIPHER_CTX *EVP_CIPHER_CTX_new();
-    void EVP_CIPHER_CTX_free(EVP_CIPHER_CTX *ctx);
-
-    int EVP_CipherInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *cipher,
-                          ENGINE *impl, const unsigned char *key,
-                          const unsigned char *iv, int enc);
-    int EVP_CipherUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl,
-                     const unsigned char *in, int inl);
-    int EVP_CipherFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl);
-
-    int crypto_EVP_CIPHER_iv_length(const EVP_CIPHER *cipher);
-    int crypto_EVP_CIPHER_key_length(const EVP_CIPHER *cipher);
-
-    int EVP_CIPHER_block_size(const EVP_CIPHER *cipher);
-    const EVP_CIPHER *EVP_get_cipherbyname(const char *name);
+    enum crypto_algo {
+        CRYPTO_ALGO_NONE,
+        CRYPTO_ALGO_AES128,
+        CRYPTO_ALGO_AES192,
+        CRYPTO_ALGO_AES256,
+        CRYPTO_ALGO_DES,
+    };
+
+    enum crypto_mode {
+        CRYPTO_MODE_ECB,
+        CRYPTO_MODE_CBC,
+        CRYPTO_MODE_CFB,
+        CRYPTO_MODE_OFB,
+    };
+
+    enum crypto_direction {
+        CRYPTO_DIR_DECRYPT = 0,
+        CRYPTO_DIR_ENCRYPT = 1,
+    };
+
+    struct crypto_stream;
+
+    struct crypto_stream *
+    crypto_stream_new(enum crypto_algo algo, enum crypto_mode mode,
+                      enum crypto_direction dir);
+
+    int
+    crypto_stream_begin(struct crypto_stream *s, const char *key, int key_size,
+                        const char *iv, int iv_size);
+
+    int
+    crypto_stream_append(struct crypto_stream *s, const char *in, int in_size,
+                         char *out, int out_size);
+
+    int
+    crypto_stream_commit(struct crypto_stream *s, char *out, int out_size);
+
+    void
+    crypto_stream_delete(struct crypto_stream *s);
 ]]
 
 local function openssl_err_str()
@@ -206,110 +228,90 @@ hmac_mt = {
     }
 }
 
-local ciphers = {}
-for algo, algo_name in pairs({des = 'DES', aes128 = 'AES-128',
-    aes192 = 'AES-192', aes256 = 'AES-256'}) do
-    local algo_api = {}
-    for mode, mode_name in pairs({cfb = 'CFB', ofb = 'OFB',
-        cbc = 'CBC', ecb = 'ECB'}) do
-            local cipher =
-                ffi.C.EVP_get_cipherbyname(algo_name .. '-' .. mode_name)
-            if cipher ~= nil then
-                algo_api[mode] = cipher
-            end
-    end
-    if algo_api ~= {} then
-        ciphers[algo] = algo_api
-    end
-end
+local crypto_stream_mt = {}
 
-local cipher_mt = {}
-
-local function cipher_gc(ctx)
-    ffi.C.EVP_CIPHER_CTX_free(ctx)
+local function crypto_stream_gc(ctx)
+    ffi.C.crypto_stream_delete(ctx)
 end
 
-local function cipher_new(cipher, key, iv, direction)
-    local needed_len = ffi.C.crypto_EVP_CIPHER_key_length(cipher)
-    if key == nil or key:len() ~= needed_len then
-        return error('Key length should be equal to cipher key length ('..
-                     tostring(needed_len)..' bytes)')
-    end
-    needed_len = ffi.C.crypto_EVP_CIPHER_iv_length(cipher)
-    if iv == nil or iv:len() ~= needed_len then
-        return error('Initial vector length should be equal to cipher iv '..
-                     'length ('..tostring(needed_len)..' bytes)')
-    end
-    local ctx = ffi.C.EVP_CIPHER_CTX_new()
+local function crypto_stream_new(algo, mode, key, iv, direction)
+    local ctx = ffi.C.crypto_stream_new(algo, mode, direction)
     if ctx == nil then
-        return error('Can\'t create cipher ctx: ' .. openssl_err_str())
+        box.error()
     end
-    ffi.gc(ctx, cipher_gc)
+    ffi.gc(ctx, crypto_stream_gc)
     local self = setmetatable({
         ctx = ctx,
-        cipher = cipher,
-        block_size = ffi.C.EVP_CIPHER_block_size(cipher),
-        direction = direction,
         buf = buffer.ibuf(),
-        initialized = false,
-        outl = ffi.new('int[1]')
-    }, cipher_mt)
+        is_initialized = false,
+    }, crypto_stream_mt)
     self:init(key, iv)
     return self
 end
 
-local function cipher_init(self, key, iv)
-    if self.ctx == nil then
+local function crypto_stream_begin(self, key, iv)
+    local ctx = self.ctx
+    if not ctx then
         return error('Cipher context isn\'t usable')
     end
-    if ffi.C.EVP_CipherInit_ex(self.ctx, self.cipher, nil,
-        key, iv, self.direction) ~= 1 then
-        return error('Can\'t init cipher:' .. openssl_err_str())
+    self.key = key or self.key
+    self.iv = iv or self.iv
+    if self.key and self.iv then
+        if ffi.C.crypto_stream_begin(ctx, self.key, self.key:len(),
+                                     self.iv, self.iv:len()) ~= 0 then
+            box.error()
+        end
+        self.is_initialized = true
     end
-    self.initialized = true
 end
 
-local function cipher_update(self, input)
-    if not self.initialized then
+local function crypto_stream_append(self, input)
+    if not self.is_initialized then
         return error('Cipher not initialized')
     end
     if type(input) ~= 'string' then
         error("Usage: cipher:update(string)")
     end
-    local wpos = self.buf:reserve(input:len() + self.block_size - 1)
-    if ffi.C.EVP_CipherUpdate(self.ctx, wpos, self.outl, input, input:len()) ~= 1 then
-        return error('Can\'t update cipher:' .. openssl_err_str())
+    local append = ffi.C.crypto_stream_append
+    local out_size = append(self.ctx, input, input:len(), nil, 0)
+    local wpos = self.buf:reserve(out_size)
+    out_size = append(self.ctx, input, input:len(), wpos, out_size)
+    if out_size < 0 then
+        box.error()
     end
-    return ffi.string(wpos, self.outl[0])
+    return ffi.string(wpos, out_size)
 end
 
-local function cipher_final(self)
-    if not self.initialized then
+local function crypto_stream_commit(self)
+    if not self.is_initialized then
         return error('Cipher not initialized')
     end
-    self.initialized = false
-    local wpos = self.buf:reserve(self.block_size - 1)
-    if ffi.C.EVP_CipherFinal_ex(self.ctx, wpos, self.outl) ~= 1 then
-        return error('Can\'t finalize cipher:' .. openssl_err_str())
+    local commit = ffi.C.crypto_stream_commit
+    local out_size = commit(self.ctx, nil, 0)
+    local wpos = self.buf:reserve(out_size)
+    out_size = commit(self.ctx, wpos, out_size)
+    if out_size < 0 then
+        box.error()
     end
-    self.initialized = false
-    return ffi.string(wpos, self.outl[0])
+    self.is_initialized = false
+    return ffi.string(wpos, out_size)
 end
 
-local function cipher_free(self)
-    ffi.C.EVP_CIPHER_CTX_free(self.ctx)
+local function crypto_stream_free(self)
     ffi.gc(self.ctx, nil)
+    crypto_stream_gc(self.ctx)
     self.ctx = nil
-    self.initialized = false
-    self.buf:reset()
+    self.key = nil
+    self.iv = nil
+    self.is_initialized = false
 end
 
-cipher_mt = {
+crypto_stream_mt = {
     __index = {
-          init = cipher_init,
-          update = cipher_update,
-          result = cipher_final,
-          free = cipher_free
+          init = crypto_stream_begin,
+          update = crypto_stream_append,
+          result = crypto_stream_commit,
+          free = crypto_stream_free
     }
 }
 
@@ -365,23 +367,51 @@ hmac_api = setmetatable(hmac_api,
         return error('HMAC method "' .. digest .. '" is not supported')
     end })
 
-local function cipher_mode_error(self, mode)
-  error('Cipher mode ' .. mode .. ' is not supported')
-end
+local crypto_algos = {
+    aes128 = ffi.C.CRYPTO_ALGO_AES128,
+    aes192 = ffi.C.CRYPTO_ALGO_AES192,
+    aes256 = ffi.C.CRYPTO_ALGO_AES256,
+    des = ffi.C.CRYPTO_ALGO_DES
+}
+local crypto_modes = {
+    ecb = ffi.C.CRYPTO_MODE_ECB,
+    cbc = ffi.C.CRYPTO_MODE_CBC,
+    cfb = ffi.C.CRYPTO_MODE_CFB,
+    ofb = ffi.C.CRYPTO_MODE_OFB
+}
+local crypto_dirs = {
+    encrypt = ffi.C.CRYPTO_DIR_ENCRYPT,
+    decrypt = ffi.C.CRYPTO_DIR_DECRYPT
+}
 
-local cipher_api = {}
-for class, subclass in pairs(ciphers) do
-    local class_api = {}
-    for subclass, cipher in pairs(subclass) do
-        class_api[subclass] = {}
-        for direction, param in pairs({encrypt = 1, decrypt = 0}) do
-              class_api[subclass][direction] = setmetatable({
-                new = function (key, iv)
-                    return cipher_new(cipher, key, iv, param)
+local algo_api_mt = {
+    __index = function(self, mode)
+        error('Cipher mode ' .. mode .. ' is not supported')
+    end
+}
+local crypto_api_mt = {
+    __index = function(self, cipher)
+        return error('Cipher method "' .. cipher .. '" is not supported')
+    end
+}
+
+local crypto_api = setmetatable({}, crypto_api_mt)
+for algo_name, algo_value in pairs(crypto_algos) do
+    local algo_api = setmetatable({}, algo_api_mt)
+    crypto_api[algo_name] = algo_api
+    for mode_name, mode_value in pairs(crypto_modes) do
+        local mode_api = {}
+        algo_api[mode_name] = mode_api
+        for dir_name, dir_value in pairs(crypto_dirs) do
+            mode_api[dir_name] = setmetatable({
+                new = function(key, iv)
+                    return crypto_stream_new(algo_value, mode_value, key, iv,
+                                             dir_value)
                 end
             }, {
-                __call = function (self, str, key, iv)
-                    local ctx = cipher_new(cipher, key, iv, param)
+                __call = function(self, str, key, iv)
+                    local ctx = crypto_stream_new(algo_value, mode_value, key,
+                                                  iv, dir_value)
                     local res = ctx:update(str)
                     res = res .. ctx:result()
                     ctx:free()
@@ -390,19 +420,10 @@ for class, subclass in pairs(ciphers) do
             })
         end
     end
-    class_api = setmetatable(class_api, {__index = cipher_mode_error})
-    if class_api ~= {} then
-        cipher_api[class] = class_api
-    end
 end
 
-cipher_api = setmetatable(cipher_api,
-    {__index = function(self, cipher)
-        return error('Cipher method "' .. cipher .. '" is not supported')
-    end })
-
 return {
     digest = digest_api,
     hmac   = hmac_api,
-    cipher = cipher_api,
+    cipher = crypto_api,
 }
diff --git a/test/app/crypto.result b/test/app/crypto.result
index 2d939b6fc..7bfb4d198 100644
--- a/test/app/crypto.result
+++ b/test/app/crypto.result
@@ -32,62 +32,102 @@ ciph.decrypt(enc, pass, iv)
 ---
 - test
 ...
---Failing scenaries
+-- Failing scenarios.
 crypto.cipher.aes128.cbc.encrypt('a')
 ---
-- error: 'builtin/crypto.lua:<line>"]: Key length should be equal to cipher key length
-    (16 bytes)'
+- error: 'builtin/crypto.lua:<line>"]: Cipher not initialized'
 ...
 crypto.cipher.aes128.cbc.encrypt('a', '123456', '435')
 ---
-- error: 'builtin/crypto.lua:<line>"]: Key length should be equal to cipher key length
-    (16 bytes)'
+- error: key size expected 16, got 6
 ...
 crypto.cipher.aes128.cbc.encrypt('a', '1234567887654321')
 ---
-- error: 'builtin/crypto.lua:<line>"]: Initial vector length should be equal to cipher
-    iv length (16 bytes)'
+- error: 'builtin/crypto.lua:<line>"]: Cipher not initialized'
 ...
 crypto.cipher.aes128.cbc.encrypt('a', '1234567887654321', '12')
 ---
-- error: 'builtin/crypto.lua:<line>"]: Initial vector length should be equal to cipher
-    iv length (16 bytes)'
+- error: IV size expected 16, got 2
 ...
 crypto.cipher.aes256.cbc.decrypt('a')
 ---
-- error: 'builtin/crypto.lua:<line>"]: Key length should be equal to cipher key length
-    (32 bytes)'
+- error: 'builtin/crypto.lua:<line>"]: Cipher not initialized'
 ...
 crypto.cipher.aes256.cbc.decrypt('a', '123456', '435')
 ---
-- error: 'builtin/crypto.lua:<line>"]: Key length should be equal to cipher key length
-    (32 bytes)'
+- error: key size expected 32, got 6
 ...
 crypto.cipher.aes256.cbc.decrypt('a', '12345678876543211234567887654321')
 ---
-- error: 'builtin/crypto.lua:<line>"]: Initial vector length should be equal to cipher
-    iv length (16 bytes)'
+- error: 'builtin/crypto.lua:<line>"]: Cipher not initialized'
 ...
 crypto.cipher.aes256.cbc.decrypt('12', '12345678876543211234567887654321', '12')
 ---
-- error: 'builtin/crypto.lua:<line>"]: Initial vector length should be equal to cipher
-    iv length (16 bytes)'
+- error: IV size expected 16, got 2
 ...
-crypto.cipher.aes192.cbc.encrypt.new()
+crypto.cipher.aes192.cbc.decrypt.new('123456788765432112345678', '12345')
 ---
-- error: Key length should be equal to cipher key length (24 bytes)
+- error: IV size expected 16, got 5
 ...
-crypto.cipher.aes192.cbc.encrypt.new('123321')
+-- Set key after codec creation.
+c = crypto.cipher.aes128.cbc.encrypt.new()
 ---
-- error: Key length should be equal to cipher key length (24 bytes)
 ...
-crypto.cipher.aes192.cbc.decrypt.new('123456788765432112345678')
+key = '1234567812345678'
 ---
-- error: Initial vector length should be equal to cipher iv length (16 bytes)
 ...
-crypto.cipher.aes192.cbc.decrypt.new('123456788765432112345678', '12345')
+iv = key
+---
+...
+c:init(key)
+---
+...
+c:update('plain')
+---
+- error: Cipher not initialized
+...
+c:result()
+---
+- error: Cipher not initialized
+...
+c:init(nil, iv)
+---
+...
+cipher = c:update('plain ')
+---
+...
+cipher = cipher..c:update('next plain')
+---
+...
+cipher = cipher..c:result()
+---
+...
+crypto.cipher.aes128.cbc.decrypt(cipher, key, iv)
+---
+- plain next plain
+...
+-- Reuse.
+key2 = '8765432187654321'
 ---
-- error: Initial vector length should be equal to cipher iv length (16 bytes)
+...
+iv2 = key2
+---
+...
+c:init(key2, iv2)
+---
+...
+cipher = c:update('new plain ')
+---
+...
+cipher = cipher..c:update('next new plain')
+---
+...
+cipher = cipher..c:result()
+---
+...
+crypto.cipher.aes128.cbc.decrypt(cipher, key2, iv2)
+---
+- new plain next new plain
 ...
 crypto.cipher.aes100.efb
 ---
@@ -103,6 +143,22 @@ crypto.digest.nodigest
 - error: '[string "return crypto.digest.nodigest "]:1: Digest method "nodigest" is
     not supported'
 ...
+-- Check that GC really drops unused codecs and streams, and
+-- nothing crashes.
+weak = setmetatable({obj = c}, {__mode = 'v'})
+---
+...
+c = nil
+---
+...
+collectgarbage('collect')
+---
+- 0
+...
+weak.obj
+---
+- null
+...
 bad_pass = '8765432112345678'
 ---
 ...
@@ -111,13 +167,13 @@ bad_iv = '123456abcdefghij'
 ...
 ciph.decrypt(enc, bad_pass, iv)
 ---
-- error: 'builtin/crypto.lua:<line>"]: Can''t finalize cipher:error:06065064:digital envelope
-    routines:EVP_DecryptFinal_ex:bad decrypt'
+- error: 'OpenSSL error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad
+    decrypt'
 ...
 ciph.decrypt(enc, pass, bad_iv)
 ---
-- error: 'builtin/crypto.lua:<line>"]: Can''t finalize cipher:error:06065064:digital envelope
-    routines:EVP_DecryptFinal_ex:bad decrypt'
+- error: 'OpenSSL error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad
+    decrypt'
 ...
 test_run:cmd("clear filter")
 ---
diff --git a/test/app/crypto.test.lua b/test/app/crypto.test.lua
index 94f594bcc..16d8ce2a7 100644
--- a/test/app/crypto.test.lua
+++ b/test/app/crypto.test.lua
@@ -12,7 +12,7 @@ enc
 ciph.decrypt(enc, pass, iv)
 
 
---Failing scenaries
+-- Failing scenarios.
 crypto.cipher.aes128.cbc.encrypt('a')
 crypto.cipher.aes128.cbc.encrypt('a', '123456', '435')
 crypto.cipher.aes128.cbc.encrypt('a', '1234567887654321')
@@ -23,16 +23,40 @@ crypto.cipher.aes256.cbc.decrypt('a', '123456', '435')
 crypto.cipher.aes256.cbc.decrypt('a', '12345678876543211234567887654321')
 crypto.cipher.aes256.cbc.decrypt('12', '12345678876543211234567887654321', '12')
 
-crypto.cipher.aes192.cbc.encrypt.new()
-crypto.cipher.aes192.cbc.encrypt.new('123321')
-crypto.cipher.aes192.cbc.decrypt.new('123456788765432112345678')
 crypto.cipher.aes192.cbc.decrypt.new('123456788765432112345678', '12345')
 
+-- Set key after codec creation.
+c = crypto.cipher.aes128.cbc.encrypt.new()
+key = '1234567812345678'
+iv = key
+c:init(key)
+c:update('plain')
+c:result()
+c:init(nil, iv)
+cipher = c:update('plain ')
+cipher = cipher..c:update('next plain')
+cipher = cipher..c:result()
+crypto.cipher.aes128.cbc.decrypt(cipher, key, iv)
+-- Reuse.
+key2 = '8765432187654321'
+iv2 = key2
+c:init(key2, iv2)
+cipher = c:update('new plain ')
+cipher = cipher..c:update('next new plain')
+cipher = cipher..c:result()
+crypto.cipher.aes128.cbc.decrypt(cipher, key2, iv2)
+
 crypto.cipher.aes100.efb
 crypto.cipher.aes256.nomode
 
 crypto.digest.nodigest
 
+-- Check that GC really drops unused codecs and streams, and
+-- nothing crashes.
+weak = setmetatable({obj = c}, {__mode = 'v'})
+c = nil
+collectgarbage('collect')
+weak.obj
 
 bad_pass = '8765432112345678'
 bad_iv = '123456abcdefghij'
-- 
2.20.1 (Apple Git-117)

  parent reply	other threads:[~2019-05-07 20:54 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-05-07 20:53 [tarantool-patches] [PATCH v3 0/4] crypto lib Vladislav Shpilevoy
2019-05-07 20:53 ` [tarantool-patches] [PATCH v3 1/4] crypto: move crypto business into a separate library Vladislav Shpilevoy
2019-05-15  7:58   ` [tarantool-patches] " Георгий Кириченко
2019-05-07 20:53 ` [tarantool-patches] [PATCH v3 2/4] crypto: make exported methods conform code style Vladislav Shpilevoy
2019-05-15  7:58   ` [tarantool-patches] " Георгий Кириченко
2019-05-07 20:53 ` [tarantool-patches] [PATCH v3 3/4] crypto: implement crypto libary Vladislav Shpilevoy
2019-05-15  7:58   ` [tarantool-patches] " Георгий Кириченко
2019-05-07 20:53 ` Vladislav Shpilevoy [this message]
2019-05-15  8:01   ` [tarantool-patches] Re: [PATCH v3 4/4] crypto: use crypto library in crypto.lua Георгий Кириченко
2019-05-15 13:42     ` Vladislav Shpilevoy
2019-05-15 13:42 ` [tarantool-patches] Re: [PATCH v3 0/4] crypto lib Vladislav Shpilevoy

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=470c7d23d93014f4c03bb7f1085a33cc8e249097.1557262174.git.v.shpilevoy@tarantool.org \
    --to=v.shpilevoy@tarantool.org \
    --cc=georgy@tarantool.org \
    --cc=kostja@tarantool.org \
    --cc=tarantool-patches@freelists.org \
    --subject='Re: [tarantool-patches] [PATCH v3 4/4] crypto: use crypto library in crypto.lua' \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox