From c8dfe0fa37b20f84c1a7ce2eae1c812c1d2457b9 Mon Sep 17 00:00:00 2001 From: Vsevolod Stakhov Date: Mon, 4 May 2020 18:07:02 +0100 Subject: [PATCH] [Feature] Lua_cryptobox: Add secretbox API --- src/lua/lua_cryptobox.c | 254 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 254 insertions(+) diff --git a/src/lua/lua_cryptobox.c b/src/lua/lua_cryptobox.c index 784abea65..12f3e063f 100644 --- a/src/lua/lua_cryptobox.c +++ b/src/lua/lua_cryptobox.c @@ -100,6 +100,12 @@ LUA_FUNCTION_DEF (cryptobox, decrypt_cookie); LUA_FUNCTION_DEF (cryptobox, pbkdf); LUA_FUNCTION_DEF (cryptobox, gen_dkim_keypair); +/* Secretbox API: uses libsodium secretbox and blake2b for key derivation */ +LUA_FUNCTION_DEF (cryptobox_secretbox, create); +LUA_FUNCTION_DEF (cryptobox_secretbox, encrypt); +LUA_FUNCTION_DEF (cryptobox_secretbox, decrypt); +LUA_FUNCTION_DEF (cryptobox_secretbox, gc); + static const struct luaL_reg cryptoboxlib_f[] = { LUA_INTERFACE_DEF (cryptobox, verify_memory), LUA_INTERFACE_DEF (cryptobox, verify_file), @@ -184,6 +190,22 @@ static const struct luaL_reg cryptoboxhashlib_m[] = { }; +static const struct luaL_reg cryptoboxsecretboxlib_f[] = { + LUA_INTERFACE_DEF (cryptobox_secretbox, create), + {NULL, NULL}, +}; + +static const struct luaL_reg cryptoboxsecretboxlib_m[] = { + LUA_INTERFACE_DEF (cryptobox_secretbox, encrypt), + LUA_INTERFACE_DEF (cryptobox_secretbox, decrypt), + {"__gc", lua_cryptobox_secretbox_gc}, + {NULL, NULL}, +}; + +struct rspamd_lua_cryptobox_secretbox { + guchar sk[crypto_secretbox_KEYBYTES]; +}; + static struct rspamd_cryptobox_pubkey * lua_check_cryptobox_pubkey (lua_State * L, int pos) { @@ -220,6 +242,15 @@ lua_check_cryptobox_hash (lua_State * L, int pos) return ud ? *((struct rspamd_lua_cryptobox_hash **)ud) : NULL; } +static struct rspamd_lua_cryptobox_secretbox * +lua_check_cryptobox_secretbox (lua_State * L, int pos) +{ + void *ud = rspamd_lua_check_udata (L, pos, "rspamd{cryptobox_secretbox}"); + + luaL_argcheck (L, ud != NULL, 1, "'cryptobox_secretbox' expected"); + return ud ? *((struct rspamd_lua_cryptobox_secretbox **)ud) : NULL; +} + /*** * @function rspamd_cryptobox_pubkey.load(file[, type[, alg]]) * Loads public key from base32 encoded file @@ -2438,6 +2469,214 @@ lua_cryptobox_gen_dkim_keypair (lua_State *L) return 2; } +/* + * Secretbox API + */ +/* Ensure that KDF output is suitable for crypto_secretbox_KEYBYTES */ +#ifdef crypto_generichash_BYTES_MIN +G_STATIC_ASSERT(crypto_secretbox_KEYBYTES >= crypto_generichash_BYTES_MIN); +#endif + +/*** + * @function rspamd_cryptobox_secretbox.create(secret_string, [params]) + * Generates a secretbox state by expanding secret string + * @param {string/text} secret_string secret string (should have high enough entropy) + * @param {table} params optional parameters - NYI + * @return {rspamd_cryptobox_secretbox} opaque object with the key expanded + */ +static gint +lua_cryptobox_secretbox_create (lua_State *L) +{ + const gchar *in; + gsize inlen; + + + if (lua_isstring (L, 1)) { + in = lua_tolstring (L, 1, &inlen); + } + else if (lua_isuserdata (L, 1)) { + struct rspamd_lua_text *t = lua_check_text (L, 1); + + if (!t) { + return luaL_error (L, "invalid arguments; userdata is not text"); + } + + in = t->start; + inlen = t->len; + } + else { + return luaL_error (L, "invalid arguments; userdata or string are expected"); + } + + if (in == NULL || inlen == 0) { + return luaL_error (L, "invalid arguments; non empty secret expected"); + } + + struct rspamd_lua_cryptobox_secretbox *sbox, **psbox; + + sbox = g_malloc0 (sizeof (*sbox)); + crypto_generichash (sbox->sk, sizeof (sbox->sk), in, inlen, NULL, 0); + psbox = lua_newuserdata (L, sizeof (*psbox)); + *psbox = sbox; + rspamd_lua_setclass (L, "rspamd{cryptobox_secretbox}", -1); + + return 1; +} + + +static gint +lua_cryptobox_secretbox_gc (lua_State *L) +{ + struct rspamd_lua_cryptobox_secretbox *sbox = + lua_check_cryptobox_secretbox (L, 1); + + if (sbox != NULL) { + sodium_memzero (sbox->sk, sizeof (sbox->sk)); + g_free (sbox); + } + else { + return luaL_error (L, "invalid arguments"); + } + + return 0; +} + +/*** + * @method rspamd_cryptobox_secretbox:encrypt(input) + * Encrypts data using secretbox. MAC is prepended to the message + * @param {string/text} input input to encrypt + * @param {table} params optional parameters - NYI + * @return {rspamd_text},{rspamd_text} nonce + output with mac + */ +static gint +lua_cryptobox_secretbox_encrypt (lua_State *L) +{ + const gchar *in; + gsize inlen; + struct rspamd_lua_cryptobox_secretbox *sbox = + lua_check_cryptobox_secretbox (L, 1); + struct rspamd_lua_text *out, *nonce; + + if (sbox == NULL) { + return luaL_error (L, "invalid arguments"); + } + + if (lua_isstring (L, 2)) { + in = lua_tolstring (L, 2, &inlen); + } + else if (lua_isuserdata (L, 2)) { + struct rspamd_lua_text *t = lua_check_text (L, 2); + + if (!t) { + return luaL_error (L, "invalid arguments; userdata is not text"); + } + + in = t->start; + inlen = t->len; + } + else { + return luaL_error (L, "invalid arguments; userdata or string are expected"); + } + + nonce = lua_new_text (L, NULL, crypto_secretbox_NONCEBYTES, TRUE); + out = lua_new_text (L, NULL, inlen + crypto_secretbox_MACBYTES, + TRUE); + + randombytes_buf ((guchar *)nonce->start, nonce->len); + crypto_secretbox_easy ((guchar *)out->start, in, inlen, nonce->start, sbox->sk); + + return 2; +} + +/*** + * @method rspamd_cryptobox_secretbox:decrypt(nonce, input) + * Decrypts data using secretbox + * @param {string/text} nonce nonce used to encrypt + * @param {string/text} input input to decrypt + * @param {table} params optional parameters - NYI + * @return {boolean},{rspamd_text} decryption result + decrypted text + */ +static gint +lua_cryptobox_secretbox_decrypt (lua_State *L) +{ + const gchar *in, *nonce; + gsize inlen, nlen; + struct rspamd_lua_cryptobox_secretbox *sbox = + lua_check_cryptobox_secretbox (L, 1); + struct rspamd_lua_text *out; + + if (sbox == NULL) { + return luaL_error (L, "invalid arguments"); + } + + /* Nonce argument */ + if (lua_isstring (L, 2)) { + nonce = lua_tolstring (L, 2, &nlen); + } + else if (lua_isuserdata (L, 2)) { + struct rspamd_lua_text *t = lua_check_text (L, 2); + + if (!t) { + return luaL_error (L, "invalid arguments; userdata is not text"); + } + + nonce = t->start; + nlen = t->len; + } + else { + return luaL_error (L, "invalid arguments; userdata or string are expected"); + } + + /* Input argument */ + if (lua_isstring (L, 3)) { + in = lua_tolstring (L, 3, &inlen); + } + else if (lua_isuserdata (L, 3)) { + struct rspamd_lua_text *t = lua_check_text (L, 3); + + if (!t) { + return luaL_error (L, "invalid arguments; userdata is not text"); + } + + in = t->start; + inlen = t->len; + } + else { + return luaL_error (L, "invalid arguments; userdata or string are expected"); + } + + if (nlen != crypto_secretbox_NONCEBYTES) { + lua_pushboolean (L, false); + lua_pushstring (L, "invalid nonce"); + return 2; + } + + if (inlen < crypto_secretbox_MACBYTES) { + lua_pushboolean (L, false); + lua_pushstring (L, "too short"); + return 2; + } + + out = lua_new_text (L, NULL, inlen - crypto_secretbox_MACBYTES, + TRUE); + gint text_pos = lua_gettop (L); + + if (crypto_secretbox_easy ((guchar *)out->start, in, inlen, + nonce, sbox->sk) == 0) { + lua_pushboolean (L, true); + lua_pushvalue (L, text_pos); /* Prevent gc by copying in stack */ + } + else { + lua_pushboolean (L, false); + lua_pushstring (L, "authentication error"); + } + + /* This causes gc method if decryption has failed */ + lua_remove (L, text_pos); + + return 2; +} + static gint lua_load_pubkey (lua_State * L) { @@ -2474,6 +2713,15 @@ lua_load_hash (lua_State * L) return 1; } +static gint +lua_load_cryptobox_secretbox (lua_State * L) +{ + lua_newtable (L); + luaL_register (L, NULL, cryptoboxhashlib_f); + + return 1; +} + static gint lua_load_cryptobox (lua_State * L) { @@ -2502,6 +2750,12 @@ luaopen_cryptobox (lua_State * L) lua_pop (L, 1); rspamd_lua_add_preload (L, "rspamd_cryptobox_hash", lua_load_hash); + rspamd_lua_new_class (L, "rspamd{cryptobox_secretbox}", + cryptoboxsecretboxlib_m); + lua_pop (L, 1); + rspamd_lua_add_preload (L, "rspamd_cryptobox_secretbox", + lua_load_cryptobox_secretbox); + rspamd_lua_add_preload (L, "rspamd_cryptobox", lua_load_cryptobox); lua_settop (L, 0); -- 2.39.5