]> source.dussan.org Git - rspamd.git/commitdiff
[Feature] Lua_cryptobox: Add secretbox API
authorVsevolod Stakhov <vsevolod@highsecure.ru>
Mon, 4 May 2020 17:07:02 +0000 (18:07 +0100)
committerVsevolod Stakhov <vsevolod@highsecure.ru>
Mon, 4 May 2020 17:07:18 +0000 (18:07 +0100)
src/lua/lua_cryptobox.c

index 784abea656510a21c3fd69d380a45bc774f4e43c..12f3e063f7b26119bdbcf687eb2a14bf99c215d9 100644 (file)
@@ -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);