]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
lua: expose hashing functions to lua scripts
authorJason Ish <jason.ish@oisf.net>
Tue, 21 Jan 2025 21:23:15 +0000 (15:23 -0600)
committerVictor Julien <victor@inliniac.net>
Thu, 23 Jan 2025 18:10:50 +0000 (19:10 +0100)
Expose md5, sha1, and sha256 to Lua scripts with
`require("suricata.hashing")`.

Ticket: 7073

src/Makefile.am
src/util-lua-hashlib.c [new file with mode: 0644]
src/util-lua-hashlib.h [new file with mode: 0644]
src/util-lua-sandbox.c

index 61581695354228a074fe635a484fef1cb1fdb829..9065c8d7091093108e3a472be1b2a64098bb9854 100755 (executable)
@@ -512,6 +512,7 @@ noinst_HEADERS = \
        util-lua-dnp3-objects.h \
        util-lua-dns.h \
        util-lua.h \
+       util-lua-hashlib.h \
        util-lua-hassh.h \
        util-lua-http.h \
        util-lua-ja3.h \
@@ -1060,6 +1061,7 @@ libsuricata_c_a_SOURCES = \
        util-lua-dnp3.c \
        util-lua-dnp3-objects.c \
        util-lua-dns.c \
+       util-lua-hashlib.c \
        util-lua-hassh.c \
        util-lua-http.c \
        util-lua-ja3.c \
diff --git a/src/util-lua-hashlib.c b/src/util-lua-hashlib.c
new file mode 100644 (file)
index 0000000..537d885
--- /dev/null
@@ -0,0 +1,430 @@
+/* Copyright (C) 2025 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * Hashing library for Lua.
+ *
+ * Usage:
+ *
+ * local hashing = require("suricata.hashing")
+ *
+ * -- One shot hash
+ * hash = hashing.sha256_digest("www.suricata.io")
+ *
+ * -- Incremental hashing
+ * hasher = hashing.sha256()
+ * hasher:update("www.")
+ * hasher:update("suricata.io")
+ * hash = hasher:finalize()
+ *
+ * Support hashes:
+ *
+ * - sha256: sha256(), sha256_digest()
+ * - sha1: sha1(), sha1_digest()
+ * - md5: md5(), md5_digest()
+ */
+
+#include "util-lua-hashlib.h"
+
+#include "lauxlib.h"
+#include "rust-bindings.h"
+
+#define SHA256_MT "suricata:hashlib:sha256"
+#define SHA1_MT   "suricata:hashlib:sha1"
+#define MD5_MT    "suricata:hashlib:md5"
+
+/**
+ * \brief Create a new SHA-256 hash instance.
+ */
+static int LuaHashLibSha256New(lua_State *L)
+{
+    struct SCSha256 **hasher = lua_newuserdata(L, sizeof(struct SCSha256 *));
+    if (hasher == NULL) {
+        return luaL_error(L, "failed to allocate userdata for sha256");
+    }
+    *hasher = SCSha256New();
+    luaL_getmetatable(L, SHA256_MT);
+    lua_setmetatable(L, -2);
+    return 1;
+}
+
+/**
+ * \brief Add more data to an existing SHA-256 hash instance.
+ */
+static int LuaHashLibSha256Update(lua_State *L)
+{
+    struct SCSha256 **hasher = luaL_checkudata(L, 1, SHA256_MT);
+    if (hasher == NULL) {
+        return luaL_error(L, "null userdata");
+    }
+    size_t data_len;
+    const char *data = luaL_checklstring(L, 2, &data_len);
+    SCSha256Update(*hasher, (const uint8_t *)data, data_len);
+    return 0;
+}
+
+static int LuaHashLibSha256Finalize(lua_State *L)
+{
+    struct SCSha256 **hasher = luaL_checkudata(L, 1, SHA256_MT);
+    if (hasher == NULL) {
+        return luaL_error(L, "null userdata");
+    }
+
+    uint8_t hash[SC_SHA256_LEN];
+    SCSha256Finalize(*hasher, hash, sizeof(hash));
+    lua_pushlstring(L, (const char *)hash, sizeof(hash));
+
+    // Finalize consumes the hasher, so set to NULL so its not free'd
+    // during garbage collection.
+    *hasher = NULL;
+
+    return 1;
+}
+
+static int LuaHashLibSha256FinalizeToHex(lua_State *L)
+{
+    struct SCSha256 **hasher = luaL_checkudata(L, 1, SHA256_MT);
+    if (hasher == NULL) {
+        return luaL_error(L, "null userdata");
+    }
+
+    char hash[SC_SHA256_HEX_LEN + 1];
+    if (!SCSha256FinalizeToHex(*hasher, hash, sizeof(hash))) {
+        *hasher = NULL;
+        return luaL_error(L, "sha256 hashing failed");
+    }
+
+    lua_pushstring(L, (const char *)hash);
+
+    // Finalize consumes the hasher, so set to NULL so its not free'd
+    // during garbage collection.
+    *hasher = NULL;
+
+    return 1;
+}
+
+static int LuaHashLibSha256Digest(lua_State *L)
+{
+    size_t buf_len;
+    const char *input = luaL_checklstring(L, 1, &buf_len);
+
+    size_t output_len = SC_SHA256_LEN;
+    uint8_t output[output_len];
+    if (!SCSha256HashBuffer((uint8_t *)input, (uint32_t)buf_len, output, output_len)) {
+        return luaL_error(L, "sha256 hashing failed");
+    }
+
+    lua_pushlstring(L, (const char *)output, output_len);
+
+    return 1;
+}
+
+static int LuaHashLibSha256HexDigest(lua_State *L)
+{
+    size_t buf_len;
+    const char *input = luaL_checklstring(L, 1, &buf_len);
+
+    char output[SC_SHA256_HEX_LEN + 1];
+    if (!SCSha256HashBufferToHex((uint8_t *)input, (uint32_t)buf_len, output, sizeof(output))) {
+        return luaL_error(L, "sha256 hashing failed");
+    }
+
+    lua_pushstring(L, (const char *)output);
+    return 1;
+}
+
+static int LuaHashLibSha256Gc(lua_State *L)
+{
+    struct SCSha256 **hasher = luaL_checkudata(L, 1, SHA256_MT);
+    if (hasher && *hasher) {
+        SCSha256Free(*hasher);
+    }
+    return 0;
+}
+
+static int LuaHashLibSha1New(lua_State *L)
+{
+    struct SCSha1 **hasher = lua_newuserdata(L, sizeof(struct SCSha1 *));
+    if (hasher == NULL) {
+        return luaL_error(L, "failed to allocate userdata for sha1");
+    }
+    *hasher = SCSha1New();
+    luaL_getmetatable(L, SHA1_MT);
+    lua_setmetatable(L, -2);
+    return 1;
+}
+
+static int LuaHashLibSha1Update(lua_State *L)
+{
+    struct SCSha1 **hasher = luaL_checkudata(L, 1, SHA1_MT);
+    if (hasher == NULL) {
+        return luaL_error(L, "null userdata");
+    }
+
+    size_t data_len;
+    const char *data = luaL_checklstring(L, 2, &data_len);
+    SCSha1Update(*hasher, (const uint8_t *)data, data_len);
+    return 0;
+}
+
+static int LuaHashLibSha1Finalize(lua_State *L)
+{
+    struct SCSha1 **hasher = luaL_checkudata(L, 1, SHA1_MT);
+    if (hasher == NULL) {
+        return luaL_error(L, "null userdata");
+    }
+
+    uint8_t hash[SC_SHA1_LEN];
+    SCSha1Finalize(*hasher, hash, sizeof(hash));
+    lua_pushlstring(L, (const char *)hash, sizeof(hash));
+
+    // Finalize consumes the hasher, so set to NULL so its not free'd
+    // during garbage collection.
+    *hasher = NULL;
+
+    return 1;
+}
+
+static int LuaHashLibSha1FinalizeToHex(lua_State *L)
+{
+    struct SCSha1 **hasher = luaL_checkudata(L, 1, SHA1_MT);
+    if (hasher == NULL) {
+        return luaL_error(L, "null userdata");
+    }
+
+    char hash[SC_SHA1_HEX_LEN + 1];
+    if (!SCSha1FinalizeToHex(*hasher, hash, sizeof(hash))) {
+        *hasher = NULL;
+        return luaL_error(L, "sha1 hashing failed");
+    }
+
+    lua_pushstring(L, (const char *)hash);
+
+    // Finalize consumes the hasher, so set to NULL so its not free'd
+    // during garbage collection.
+    *hasher = NULL;
+
+    return 1;
+}
+
+static int LuaHashLibSha1Digest(lua_State *L)
+{
+    size_t buf_len;
+    const char *input = luaL_checklstring(L, 1, &buf_len);
+
+    uint8_t output[SC_SHA1_LEN];
+    if (!SCSha1HashBuffer((uint8_t *)input, (uint32_t)buf_len, output, sizeof(output))) {
+        return luaL_error(L, "sha1 hashing failed");
+    }
+
+    lua_pushlstring(L, (const char *)output, sizeof(output));
+    return 1;
+}
+
+static int LuaHashLibSha1HexDigest(lua_State *L)
+{
+    size_t buf_len;
+    const char *input = luaL_checklstring(L, 1, &buf_len);
+
+    char output[SC_SHA1_HEX_LEN + 1];
+    if (!SCSha1HashBufferToHex((uint8_t *)input, (uint32_t)buf_len, output, sizeof(output))) {
+        return luaL_error(L, "sha1 hashing failed");
+    }
+
+    lua_pushstring(L, (const char *)output);
+    return 1;
+}
+
+static int LuaHashLibSha1Gc(lua_State *L)
+{
+    struct SCSha1 **hasher = luaL_checkudata(L, 1, SHA1_MT);
+    if (hasher && *hasher) {
+        SCSha1Free(*hasher);
+    }
+    return 0;
+}
+
+static int LuaHashLibMd5New(lua_State *L)
+{
+    struct SCMd5 **hasher = lua_newuserdata(L, sizeof(struct SCMd5 *));
+    if (hasher == NULL) {
+        return luaL_error(L, "failed to allocate userdata for sha1");
+    }
+    *hasher = SCMd5New();
+    luaL_getmetatable(L, MD5_MT);
+    lua_setmetatable(L, -2);
+    return 1;
+}
+
+static int LuaHashLibMd5Update(lua_State *L)
+{
+    struct SCMd5 **hasher = luaL_checkudata(L, 1, MD5_MT);
+    if (hasher == NULL) {
+        return luaL_error(L, "null userdata");
+    }
+
+    size_t data_len;
+    const char *data = luaL_checklstring(L, 2, &data_len);
+    SCMd5Update(*hasher, (const uint8_t *)data, data_len);
+    return 0;
+}
+
+static int LuaHashLibMd5Finalize(lua_State *L)
+{
+    struct SCMd5 **hasher = luaL_checkudata(L, 1, MD5_MT);
+    if (hasher == NULL) {
+        return luaL_error(L, "null userdata");
+    }
+
+    uint8_t hash[SC_MD5_LEN];
+    SCMd5Finalize(*hasher, hash, sizeof(hash));
+    lua_pushlstring(L, (const char *)hash, sizeof(hash));
+
+    // Finalize consumes the hasher, so set to NULL so its not free'd
+    // during garbage collection.
+    *hasher = NULL;
+
+    return 1;
+}
+
+static int LuaHashLibMd5FinalizeToHex(lua_State *L)
+{
+    struct SCMd5 **hasher = luaL_checkudata(L, 1, MD5_MT);
+    if (hasher == NULL) {
+        return luaL_error(L, "null userdata");
+    }
+
+    char hash[SC_MD5_HEX_LEN + 1];
+    if (!SCMd5FinalizeToHex(*hasher, hash, sizeof(hash))) {
+        *hasher = NULL;
+        return luaL_error(L, "md5 hashing failed");
+    }
+
+    lua_pushstring(L, (const char *)hash);
+
+    // Finalize consumes the hasher, so set to NULL so its not free'd
+    // during garbage collection.
+    *hasher = NULL;
+
+    return 1;
+}
+
+static int LuaHashLibMd5Digest(lua_State *L)
+{
+    size_t buf_len;
+    const char *input = luaL_checklstring(L, 1, &buf_len);
+
+    uint8_t output[SC_MD5_LEN];
+    if (!SCMd5HashBuffer((uint8_t *)input, (uint32_t)buf_len, output, sizeof(output))) {
+        return luaL_error(L, "md5 hashing failed");
+    }
+
+    lua_pushlstring(L, (const char *)output, sizeof(output));
+    return 1;
+}
+
+static int LuaHashLibMd5HexDigest(lua_State *L)
+{
+    size_t buf_len;
+    const char *input = luaL_checklstring(L, 1, &buf_len);
+
+    char output[SC_MD5_HEX_LEN + 1];
+    if (!SCMd5HashBufferToHex((uint8_t *)input, (size_t)buf_len, output, sizeof(output))) {
+        return luaL_error(L, "md5 hashing failed");
+    }
+
+    lua_pushstring(L, (const char *)output);
+    return 1;
+}
+
+static int LuaHashLibMd5Gc(lua_State *L)
+{
+    struct SCMd5 **hasher = luaL_checkudata(L, 1, MD5_MT);
+    if (hasher && *hasher) {
+        SCMd5Free(*hasher);
+    }
+    return 0;
+}
+
+static const struct luaL_Reg hashlib[] = {
+    // clang-format off
+    { "sha256_digest", LuaHashLibSha256Digest },
+    { "sha256_hexdigest", LuaHashLibSha256HexDigest },
+    { "sha256", LuaHashLibSha256New },
+    { "sha1_digest", LuaHashLibSha1Digest },
+    { "sha1_hexdigest", LuaHashLibSha1HexDigest },
+    { "sha1", LuaHashLibSha1New },
+    { "md5_digest", LuaHashLibMd5Digest },
+    { "md5_hexdigest", LuaHashLibMd5HexDigest },
+    { "md5", LuaHashLibMd5New },
+    { NULL, NULL },
+    // clang-format on
+};
+
+static const struct luaL_Reg sha256_meta[] = {
+    // clang-format off
+    { "update", LuaHashLibSha256Update },
+    { "finalize", LuaHashLibSha256Finalize },
+    { "finalize_to_hex", LuaHashLibSha256FinalizeToHex },
+    { "__gc", LuaHashLibSha256Gc },
+    { NULL, NULL },
+    // clang-format on
+};
+
+static const struct luaL_Reg sha1_meta[] = {
+    // clang-format off
+    { "update", LuaHashLibSha1Update },
+    { "finalize", LuaHashLibSha1Finalize },
+    { "finalize_to_hex", LuaHashLibSha1FinalizeToHex },
+    { "__gc", LuaHashLibSha1Gc },
+    { NULL, NULL },
+    // clang-format on
+};
+
+static const struct luaL_Reg md5_meta[] = {
+    // clang-format off
+    { "update", LuaHashLibMd5Update },
+    { "finalize", LuaHashLibMd5Finalize },
+    { "finalize_to_hex", LuaHashLibMd5FinalizeToHex },
+    { "__gc", LuaHashLibMd5Gc },
+    { NULL, NULL },
+    // clang-format on
+};
+
+int SCLuaLoadHashlib(lua_State *L)
+{
+    luaL_newmetatable(L, SHA256_MT);
+    lua_pushvalue(L, -1);
+    lua_setfield(L, -2, "__index");
+    luaL_setfuncs(L, sha256_meta, 0);
+
+    luaL_newmetatable(L, SHA1_MT);
+    lua_pushvalue(L, -1);
+    lua_setfield(L, -2, "__index");
+    luaL_setfuncs(L, sha1_meta, 0);
+
+    luaL_newmetatable(L, MD5_MT);
+    lua_pushvalue(L, -1);
+    lua_setfield(L, -2, "__index");
+    luaL_setfuncs(L, md5_meta, 0);
+
+    luaL_newlib(L, hashlib);
+
+    return 1;
+}
diff --git a/src/util-lua-hashlib.h b/src/util-lua-hashlib.h
new file mode 100644 (file)
index 0000000..3686840
--- /dev/null
@@ -0,0 +1,26 @@
+/* Copyright (C) 2025 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#ifndef SURICATA_UTIL_LUA_HASHLIB_H
+#define SURICATA_UTIL_LUA_HASHLIB_H
+
+#include "rust.h"
+#include "lua.h"
+
+int SCLuaLoadHashlib(lua_State *l);
+
+#endif /* SURICATA_UTIL_LUA_HASHLIB_H */
index 4c4838418f390fe65b35bf13cbe322a4d118bb1f..8f63ed0ff74b53fd91ebfe955d29f27eec1769ad 100644 (file)
@@ -29,9 +29,9 @@
 #include "util-debug.h"
 
 #include "util-debug.h"
-#include "util-validate.h"
 #include "util-lua-sandbox.h"
 #include "util-lua-dataset.h"
+#include "util-lua-hashlib.h"
 
 #define SANDBOX_CTX "SANDBOX_CTX"
 
@@ -267,6 +267,9 @@ static int SCLuaSbRequire(lua_State *L)
     if (strcmp(module_name, "suricata.dataset") == 0) {
         LuaLoadDatasetLib(L);
         return 1;
+    } else if (strcmp(module_name, "suricata.hashlib") == 0) {
+        SCLuaLoadHashlib(L);
+        return 1;
     }
 
     return luaL_error(L, "Module not found: %s", module_name);