diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 63e4971e5..f82c3c2ac 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -2765,6 +2765,15 @@ It can be created via `PcgRandom(seed)` or `PcgRandom(seed, sequence)`. * This is only a rough approximation of a normal distribution with mean=(max-min)/2 and variance=1 * Increasing num_trials improves accuracy of the approximation +### `SecureRandom` +Interface for the operating system's crypto-secure PRNG. + +It can be created via `SecureRandom()`. The constructor returns nil if a secure random device cannot be +be found on the system. + +#### Methods +* `next_bytes([count])`: return next `count` (default 1, capped at 2048) many random bytes, as a string. + ### `PerlinNoise` A perlin noise generator. It can be created via `PerlinNoise(seed, octaves, persistence, scale)` diff --git a/src/porting.cpp b/src/porting.cpp index ced41d4fb..3e39fc813 100644 --- a/src/porting.cpp +++ b/src/porting.cpp @@ -29,6 +29,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include #elif defined(_WIN32) + #include + #include #include #endif #if !defined(_WIN32) @@ -701,5 +703,44 @@ v2u32 getDisplaySize() # endif // __ANDROID__ #endif // SERVER -} //namespace porting +//// +//// OS-specific Secure Random +//// + +#ifdef WIN32 + +bool secure_rand_fill_buf(void *buf, size_t len) +{ + HCRYPTPROV wctx; + + if (!CryptAcquireContext(&wctx, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) + return false; + + CryptGenRandom(wctx, len, (BYTE *)buf); + CryptReleaseContext(wctx, 0); + return true; +} + +#else + +bool secure_rand_fill_buf(void *buf, size_t len) +{ + // N.B. This function checks *only* for /dev/urandom, because on most + // common OSes it is non-blocking, whereas /dev/random is blocking, and it + // is exceptionally uncommon for there to be a situation where /dev/random + // exists but /dev/urandom does not. This guesswork is necessary since + // random devices are not covered by any POSIX standard... + FILE *fp = fopen("/dev/urandom", "rb"); + if (!fp) + return false; + + bool success = fread(buf, len, 1, fp) == 1; + + fclose(fp); + return success; +} + +#endif + +} //namespace porting diff --git a/src/porting.h b/src/porting.h index a86d37fbb..1e89cd044 100644 --- a/src/porting.h +++ b/src/porting.h @@ -343,6 +343,7 @@ void setXorgClassHint(const video::SExposedVideoData &video_data, // threads in the process inherit this exception handler void setWin32ExceptionHandler(); +bool secure_rand_fill_buf(void *buf, size_t len); } // namespace porting #ifdef __ANDROID__ diff --git a/src/script/lua_api/l_noise.cpp b/src/script/lua_api/l_noise.cpp index 6dcffa31c..04dc6048f 100644 --- a/src/script/lua_api/l_noise.cpp +++ b/src/script/lua_api/l_noise.cpp @@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "common/c_converter.h" #include "common/c_content.h" #include "log.h" +#include "porting.h" +#include "util/numeric.h" /////////////////////////////////////// /* @@ -600,3 +602,116 @@ const luaL_reg LuaPcgRandom::methods[] = { luamethod(LuaPcgRandom, rand_normal_dist), {0,0} }; + +/////////////////////////////////////// +/* + LuaSecureRandom +*/ + +bool LuaSecureRandom::fillRandBuf() +{ + return porting::secure_rand_fill_buf(m_rand_buf, RAND_BUF_SIZE); +} + +int LuaSecureRandom::l_next_bytes(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + LuaSecureRandom *o = checkobject(L, 1); + u32 count = lua_isnumber(L, 2) ? lua_tointeger(L, 2) : 1; + + // Limit count + count = MYMIN(RAND_BUF_SIZE, count); + + // Find out whether we can pass directly from our array, or have to do some gluing + size_t count_remaining = RAND_BUF_SIZE - o->m_rand_idx; + if (count_remaining >= count) { + lua_pushlstring(L, o->m_rand_buf + o->m_rand_idx, count); + o->m_rand_idx += count; + } else { + char output_buf[RAND_BUF_SIZE]; + + // Copy over with what we have left from our current buffer + memcpy(output_buf, o->m_rand_buf + o->m_rand_idx, count_remaining); + + // Refill buffer and copy over the remainder of what was requested + o->fillRandBuf(); + memcpy(output_buf + count_remaining, o->m_rand_buf, count - count_remaining); + + // Update index + o->m_rand_idx = count - count_remaining; + + lua_pushlstring(L, output_buf, count); + } + + return 1; +} + + +int LuaSecureRandom::create_object(lua_State *L) +{ + LuaSecureRandom *o = new LuaSecureRandom(); + + // Fail and return nil if we can't securely fill the buffer + if (!o->fillRandBuf()) { + delete o; + return 0; + } + + *(void **)(lua_newuserdata(L, sizeof(void *))) = o; + luaL_getmetatable(L, className); + lua_setmetatable(L, -2); + return 1; +} + + +int LuaSecureRandom::gc_object(lua_State *L) +{ + LuaSecureRandom *o = *(LuaSecureRandom **)(lua_touserdata(L, 1)); + delete o; + return 0; +} + + +LuaSecureRandom *LuaSecureRandom::checkobject(lua_State *L, int narg) +{ + luaL_checktype(L, narg, LUA_TUSERDATA); + void *ud = luaL_checkudata(L, narg, className); + if (!ud) + luaL_typerror(L, narg, className); + return *(LuaSecureRandom **)ud; +} + + +void LuaSecureRandom::Register(lua_State *L) +{ + lua_newtable(L); + int methodtable = lua_gettop(L); + luaL_newmetatable(L, className); + int metatable = lua_gettop(L); + + lua_pushliteral(L, "__metatable"); + lua_pushvalue(L, methodtable); + lua_settable(L, metatable); + + lua_pushliteral(L, "__index"); + lua_pushvalue(L, methodtable); + lua_settable(L, metatable); + + lua_pushliteral(L, "__gc"); + lua_pushcfunction(L, gc_object); + lua_settable(L, metatable); + + lua_pop(L, 1); + + luaL_openlib(L, 0, methods, 0); + lua_pop(L, 1); + + lua_register(L, className, create_object); +} + +const char LuaSecureRandom::className[] = "SecureRandom"; +const luaL_reg LuaSecureRandom::methods[] = { + luamethod(LuaSecureRandom, next_bytes), + {0,0} +}; diff --git a/src/script/lua_api/l_noise.h b/src/script/lua_api/l_noise.h index e958c5a23..492eb7550 100644 --- a/src/script/lua_api/l_noise.h +++ b/src/script/lua_api/l_noise.h @@ -160,4 +160,37 @@ public: static void Register(lua_State *L); }; + +/* + LuaSecureRandom +*/ +class LuaSecureRandom : public ModApiBase { +private: + static const size_t RAND_BUF_SIZE = 2048; + static const char className[]; + static const luaL_reg methods[]; + + u32 m_rand_idx; + char m_rand_buf[RAND_BUF_SIZE]; + + // Exported functions + + // garbage collector + static int gc_object(lua_State *L); + + // next_bytes(self, count) -> get count many bytes + static int l_next_bytes(lua_State *L); + +public: + bool fillRandBuf(); + + // LuaSecureRandom() + // Creates an LuaSecureRandom and leaves it on top of stack + static int create_object(lua_State *L); + + static LuaSecureRandom *checkobject(lua_State *L, int narg); + + static void Register(lua_State *L); +}; + #endif /* L_NOISE_H_ */ diff --git a/src/script/scripting_game.cpp b/src/script/scripting_game.cpp index 4f0350d41..33bc5c2a7 100644 --- a/src/script/scripting_game.cpp +++ b/src/script/scripting_game.cpp @@ -98,6 +98,7 @@ void GameScripting::InitializeModApi(lua_State *L, int top) LuaPerlinNoiseMap::Register(L); LuaPseudoRandom::Register(L); LuaPcgRandom::Register(L); + LuaSecureRandom::Register(L); LuaVoxelManip::Register(L); NodeMetaRef::Register(L); NodeTimerRef::Register(L);