From c0ae1ec65c000179ea948da58df3b1eb577d6488 Mon Sep 17 00:00:00 2001 From: Paul Kulchenko Date: Wed, 15 Sep 2021 18:12:04 -0700 Subject: [PATCH] Add lua-argon2 module for redbean/lua --- tool/net/largon2.c | 515 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 515 insertions(+) create mode 100644 tool/net/largon2.c diff --git a/tool/net/largon2.c b/tool/net/largon2.c new file mode 100644 index 000000000..fa1d7432d --- /dev/null +++ b/tool/net/largon2.c @@ -0,0 +1,515 @@ +/*** +Lua C binding for the Argon2 password hashing function. Compatible with Lua +5.x and LuaJIT. +See the [Argon2 documentation](https://github.com/P-H-C/phc-winner-argon2) for +in-depth instructions and details about Argon2. + +This module's version is compatible with Argon2 +[20161029](https://github.com/P-H-C/phc-winner-argon2/releases/tag/20161029) +and later. + +Note: this document is also valid for the +[lua-argon2-ffi](https://github.com/thibaultcha/lua-argon2-ffi) module: an FFI +implementation of this binding for LuaJIT which uses the same API as this +original implementaiton. + +@module argon2 +@author Thibault Charbonnier +@license MIT +@release 3.0.1 +*/ + + +#include +#include +#include +#include +#include +#include + + +#ifndef LUA_51 +#if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 502 +#define LUA_51 1 +#else +#define LUA_51 0 +#endif +#endif + + +/*** +Argon2 hashing variants. Those fields are `userdatums`, read-only values that +can be fed to the module's configuration or the `hash_encoded` function. +See the [Argon2 documentation](https://github.com/P-H-C/phc-winner-argon2) +for a description of those variants. +@field argon2_i +@field argon2_d +@field argon2_id +@table variants +*/ + + +/*** +Argon2 hashing options. Those options can be given to `hash_encoded` as a table. +If values are omitted, the default values of this module will be used. +Default values of this module can be overriden with `m_cost()`, `t_cost()`, +`parallelism()`, `hash_len()`, and `variant()`. +@field t_cost Number of iterations (`number`, default: `3`). + argon2.hash_encoded("password", "salt", { t_cost = 4 }) +Can be set to a new default in lua-argon2 (C binding only) by calling: + argon2.t_cost(4) +@field m_cost Sets memory usage as KiB (`number`, default: `4096`). + argon2.hash_encoded("password", "salt", { + m_cost = math.pow(2, 16) -- 2^16 aka 65536 KiB + }) +Can be set to a new default in lua-argon2 (C binding only) by calling: + argon2.m_cost(16) +@field parallelism Number of threads and compute lanes (`number`, default: `1`). + argon2.hash_encoded("password", "salt", { parallelism = 2 }) +Can be set to a new default in lua-argon2 (C binding only) by calling: + argon2.parallelism(2) +@field hash_len Length of the hash output length (`number`, default: `32`). + argon2.hash_encoded("password", "salt", { hash_len = 64 }) +Can be set to a new default in lua-argon2 (C binding only) by calling: + argon2.hash_len(64) +@field variant Choose the Argon2 variant to use (Argon2i, Argon2d, Argon2id) +from the `variants` table. (`userdata`, default: `argon2.variants.argon2_i`). + argon2.hash_encoded("password", "salt", { variant = argon2.variants.argon2_d }) +Can be set to a new default in lua-argon2 (C binding only) by calling: + argon2.variant(argon2.variants.argon2_i) + argon2.variant(argon2.variants.argon2_d) + argon2.variant(argon2.variants.argon2_id) +@table options +*/ +#define LUA_ARGON2_DEFAULT_T_COST 3 +#define LUA_ARGON2_DEFAULT_M_COST 4096 +#define LUA_ARGON2_DEFAULT_PARALLELISM 1 +#define LUA_ARGON2_DEFAULT_HASH_LEN 32 + + +typedef struct largon2_config_s largon2_config_t; +struct largon2_config_s { + uint32_t m_cost; + uint32_t t_cost; + uint32_t parallelism; + uint32_t hash_len; + argon2_type variant; +}; + + +/* CONFIGURATION */ + + +static void +largon2_create_config(lua_State *L) +{ + largon2_config_t *cfg; + + cfg = lua_newuserdata(L, sizeof(*cfg)); + cfg->t_cost = LUA_ARGON2_DEFAULT_T_COST; + cfg->m_cost = LUA_ARGON2_DEFAULT_M_COST; + cfg->parallelism = LUA_ARGON2_DEFAULT_PARALLELISM; + cfg->hash_len = LUA_ARGON2_DEFAULT_HASH_LEN; + cfg->variant = Argon2_i; +} + + +static largon2_config_t * +largon2_fetch_config(lua_State *L) +{ + largon2_config_t *cfg; + + cfg = lua_touserdata(L, lua_upvalueindex(1)); + if (!cfg) + luaL_error(L, "could not retrieve argon2 config"); + + return cfg; +} + + +static largon2_config_t * +largon2_arg_init(lua_State *L, int nargs) +{ + if (lua_gettop(L) > nargs) { + luaL_error(L, "expecting no more than %d arguments, but got %d", + nargs, lua_gettop(L)); + } + + lua_settop(L, nargs); + + return largon2_fetch_config(L); +} + + +static void +largon2_integer_opt(lua_State *L, uint32_t optidx, uint32_t argidx, + uint32_t *property, const char *key) +{ + uint32_t value; + char errmsg[64]; + + if (!lua_isnil(L, optidx)) { + if (lua_isnumber(L, optidx)) { + value = lua_tonumber(L, optidx); + *property = value; + + } else { + sprintf(errmsg, "expected %s to be a number, got %s", + key, luaL_typename(L, optidx)); + luaL_argerror(L, argidx, errmsg); + } + } +} + + +static int +largon2_cfg_t_cost(lua_State *L) +{ + largon2_config_t *cfg = largon2_arg_init(L, 1); + + largon2_integer_opt(L, 1, 1, &cfg->t_cost, "t_cost"); + lua_pushinteger(L, cfg->t_cost); + + return 1; +} + + +static int +largon2_cfg_m_cost(lua_State *L) +{ + largon2_config_t *cfg = largon2_arg_init(L, 1); + + largon2_integer_opt(L, 1, 1, &cfg->m_cost, "m_cost"); + lua_pushinteger(L, cfg->m_cost); + + return 1; +} + + +static int +largon2_cfg_parallelism(lua_State *L) +{ + largon2_config_t *cfg = largon2_arg_init(L, 1); + + largon2_integer_opt(L, 1, 1, &cfg->parallelism, "parallelism"); + lua_pushinteger(L, cfg->parallelism); + + return 1; +} + + +static int +largon2_cfg_hash_len(lua_State *L) +{ + largon2_config_t *cfg = largon2_arg_init(L, 1); + + largon2_integer_opt(L, 1, 1, &cfg->hash_len, "hash_len"); + lua_pushinteger(L, cfg->hash_len); + + return 1; +} + + +static int +largon2_cfg_variant(lua_State *L) +{ + largon2_config_t *cfg = largon2_arg_init(L, 1); + + luaL_checktype(L, 1, LUA_TLIGHTUSERDATA); + + cfg->variant = (argon2_type) lua_touserdata(L, 1); + + return 1; +} + + +/* BINDINGS */ + + +/*** +Hashes a password with Argon2i, Argon2d, or Argon2id, producing an encoded +hash. +@function hash_encoded +@param[type=string] plain Plain string to hash_encoded. +@param[type=string] salt Salt to use to hash the plain string. +@param[type=table] options Options with which to hash the plain string. See +`options`. This parameter is optional, if values are omitted the default ones +will be used. +@treturn string `encoded`: Encoded hash computed by Argon2, or `nil` if an +error occurred. +@treturn string `err`: `nil`, or a string describing the error if any. + +@usage +local hash, err = argon2.hash_encoded("password", "somesalt") +if err then + error("could not hash_encoded: " .. err) +end + +-- with options and variant +local hash, err = argon2.hash_encoded("password", "somesalt", { + t_cost = 4, + m_cost = math.pow(2, 16), -- 65536 KiB + variant = argon2.variants.argon2_d +}) +*/ +static int +largon2_hash_encoded(lua_State *L) +{ + const char *plain, *salt; + char *encoded, *err_msg; + size_t plainlen, saltlen; + size_t encoded_len; + uint32_t t_cost; + uint32_t m_cost; + uint32_t hash_len; + uint32_t parallelism; + argon2_type variant; + argon2_error_codes ret_code; + largon2_config_t *cfg; + luaL_Buffer buf; + + plain = luaL_checklstring(L, 1, &plainlen); + salt = luaL_checklstring(L, 2, &saltlen); + + cfg = largon2_arg_init(L, 3); + + t_cost = cfg->t_cost; + m_cost = cfg->m_cost; + parallelism = cfg->parallelism; + hash_len = cfg->hash_len; + variant = cfg->variant; + + if (!lua_isnil(L, 3)) { + if (!lua_istable(L, 3)) { + luaL_argerror(L, 3, "expected to be a table"); + } + + lua_getfield(L, 3, "t_cost"); + largon2_integer_opt(L, -1, 3, &t_cost, "t_cost"); + lua_pop(L, 1); + + lua_getfield(L, 3, "m_cost"); + largon2_integer_opt(L, -1, 3, &m_cost, "m_cost"); + lua_pop(L, 1); + + lua_getfield(L, 3, "parallelism"); + largon2_integer_opt(L, -1, 3, ¶llelism, "parallelism"); + lua_pop(L, 1); + + lua_getfield(L, 3, "hash_len"); + largon2_integer_opt(L, -1, 3, &hash_len, "hash_len"); + lua_pop(L, 1); + + lua_getfield(L, 3, "variant"); + if (!lua_isnil(L, -1)) { + if (!lua_islightuserdata(L, -1)) { + char errmsg[64]; + sprintf(errmsg, "expected variant to be a number, got %s", + luaL_typename(L, -1)); + luaL_argerror(L, 3, errmsg); + } + + variant = (argon2_type) lua_touserdata(L, -1); + } + + lua_pop(L, 1); + } + + encoded_len = argon2_encodedlen(t_cost, m_cost, parallelism, saltlen, + hash_len, variant); + +#if LUA_51 + luaL_buffinit(L, &buf); + encoded = luaL_prepbuffer(&buf); +#else + encoded = luaL_buffinitsize(L, &buf, encoded_len); +#endif + + if (variant == Argon2_d) { + ret_code = + argon2d_hash_encoded(t_cost, m_cost, parallelism, plain, plainlen, + salt, saltlen, hash_len, encoded, encoded_len); + + } else if (variant == Argon2_id) { + ret_code = + argon2id_hash_encoded(t_cost, m_cost, parallelism, plain, plainlen, + salt, saltlen, hash_len, encoded, encoded_len); + + } else { + ret_code = + argon2i_hash_encoded(t_cost, m_cost, parallelism, plain, plainlen, + salt, saltlen, hash_len, encoded, encoded_len); + } + +#if LUA_51 + luaL_addsize(&buf, encoded_len); + luaL_pushresult(&buf); +#else + luaL_pushresultsize(&buf, encoded_len); +#endif + + if (ret_code != ARGON2_OK) { + err_msg = (char *) argon2_error_message(ret_code); + lua_pushnil(L); + lua_pushstring(L, err_msg); + return 2; + } + + return 1; +} + + +/*** +Verifies a password against an encoded string. +@function verify +@param[type=string] encoded Encoded string to verify the plain password against. +@param[type=string] password Plain password to verify. +@treturn boolean `ok`: `true` if the password matches, `false` if it is a +mismatch. If an error occurs during the verification, will be `nil`. +@treturn string `err`: `nil`, or a string describing the error if any. A +password mismatch will not return an error, but will return `ok = false` +instead. + +@usage +local ok, err = argon2.verify(argon2i_hash, "password") +if err then + -- failure to verify (*not* a password mismatch) + error("could not verify: " .. err) +end + +if not ok then + -- password mismatch + error("The password does not match the supplied hash") +end + +-- with a argon2d hash +local ok, err = argon2.verify(argon2d_hash, "password") +*/ +static int +largon2_verify(lua_State *L) +{ + const char *plain, *encoded; + size_t plainlen; + argon2_type variant; + argon2_error_codes ret_code; + char *err_msg; + + if (lua_gettop(L) != 2) { + return luaL_error(L, "expecting 2 arguments, but got %d", + lua_gettop(L)); + } + + encoded = luaL_checkstring(L, 1); + plain = luaL_checklstring(L, 2, &plainlen); + + if (strstr(encoded, "argon2d")) { + variant = Argon2_d; + + } else if (strstr(encoded, "argon2id")) { + variant = Argon2_id; + + } else { + variant = Argon2_i; + } + + ret_code = argon2_verify(encoded, plain, plainlen, variant); + if (ret_code == ARGON2_VERIFY_MISMATCH) { + lua_pushboolean(L, 0); + return 1; + } + + if (ret_code != ARGON2_OK) { + err_msg = (char *) argon2_error_message(ret_code); + lua_pushnil(L); + lua_pushstring(L, err_msg); + return 2; + } + + lua_pushboolean(L, 1); + + return 1; +} + + +/* MODULE */ + + +static void +largon2_push_argon2_variants_table(lua_State *L) +{ + lua_newtable(L); + + lua_pushlightuserdata(L, (void *) Argon2_i); + lua_setfield(L, -2, "argon2_i"); + + lua_pushlightuserdata(L, (void *) Argon2_d); + lua_setfield(L, -2, "argon2_d"); + + lua_pushlightuserdata(L, (void *) Argon2_id); + lua_setfield(L, -2, "argon2_id"); + + return; +} + + +#if LUA_51 +/* Compatibility for Lua 5.1. + * + * luaL_setfuncs() is used to create a module table where the functions have + * largon2_config_t as their first upvalue. Code borrowed from Lua 5.2 source. */ +static void +compat_luaL_setfuncs(lua_State *l, const luaL_Reg *reg, int nup) +{ + int i; + + luaL_checkstack(l, nup, "too many upvalues"); + for (; reg->name != NULL; reg++) { /* fill the table with given functions */ + for (i = 0; i < nup; i++) /* copy upvalues to the top */ + lua_pushvalue(l, -nup); + lua_pushcclosure(l, reg->func, nup); /* closure with those upvalues */ + lua_setfield(l, -(nup + 2), reg->name); + } + lua_pop(l, nup); /* remove upvalues */ +} +#else +#define compat_luaL_setfuncs(L, l, nup) luaL_setfuncs(L, l, nup) +#endif + + +static const luaL_Reg largon2[] = { { "verify", largon2_verify }, + { "hash_encoded", largon2_hash_encoded }, + { "t_cost", largon2_cfg_t_cost }, + { "m_cost", largon2_cfg_m_cost }, + { "parallelism", largon2_cfg_parallelism }, + { "hash_len", largon2_cfg_hash_len }, + { "variant", largon2_cfg_variant }, + { NULL, NULL } }; + + +int +luaopen_argon2(lua_State *L) +{ + lua_newtable(L); + + largon2_create_config(L); + compat_luaL_setfuncs(L, largon2, 1); + + /* push argon2.variants table */ + + largon2_push_argon2_variants_table(L); + lua_setfield(L, -2, "variants"); + + lua_pushstring(L, "3.0.1"); + lua_setfield(L, -2, "_VERSION"); + + lua_pushstring(L, "Thibault Charbonnier"); + lua_setfield(L, -2, "_AUTHOR"); + + lua_pushstring(L, "MIT"); + lua_setfield(L, -2, "_LICENSE"); + + lua_pushstring(L, "https://github.com/thibaultcha/lua-argon2"); + lua_setfield(L, -2, "_URL"); + + return 1; +}