diff --git a/lib/flua/libhash/hash.3lua b/lib/flua/libhash/hash.3lua --- a/lib/flua/libhash/hash.3lua +++ b/lib/flua/libhash/hash.3lua @@ -21,13 +21,15 @@ .Bl -bullet -compact .It sha256 +.It +sha512 .El .Ss APIs Supported .Bl -tag -width asdf -compact .It Fn new data Compute a digest based on the .Va data . -.It Fn update Va data +.It Fn update data Using the current digest, process .Va data to compute a new digest as if all prior data had been concatenated together. @@ -47,6 +49,7 @@ .Sh EXAMPLES .Sh SEE ALSO .Xr sha256 3 +.Xr sha512 3 .Sh AUTHORS The .Nm diff --git a/lib/flua/libhash/lhash.c b/lib/flua/libhash/lhash.c --- a/lib/flua/libhash/lhash.c +++ b/lib/flua/libhash/lhash.c @@ -9,10 +9,13 @@ #include "lhash.h" #include +#include #include #define SHA256_META "SHA256 meta table" +#define SHA512_META "SHA512 meta table" #define SHA256_DIGEST_LEN 32 +#define SHA512_DIGEST_LEN 64 /* * Note C++ comments indicate the before -- after state of the stack, in with a @@ -41,6 +44,21 @@ return (1); } +static int +lua_sha512_update(lua_State *L) +{ + size_t len; + const unsigned char *data; + SHA512_CTX *ctx; + + ctx = luaL_checkudata(L, 1, SHA512_META); + data = luaL_checklstring(L, 2, &len); + SHA512_Update(ctx, data, len); + + lua_settop(L, 1); + + return (1); +} /* * Finalizes the digest value and returns it as a 32-byte binary string. The ctx @@ -58,34 +76,69 @@ return (1); } +static int +lua_sha512_digest(lua_State *L) +{ + SHA512_CTX *ctx; + unsigned char digest[SHA512_DIGEST_LEN]; + + ctx = luaL_checkudata(L, 1, SHA512_META); + SHA512_Final(digest, ctx); + lua_pushlstring(L, digest, sizeof(digest)); + + return(1); +} /* * Finalizes the digest value and returns it as a 64-byte ascii string of hex * numbers. The ctx is zeroed. */ +static void +hash_hexdigest(char *buf, unsigned char *digest, int len) +{ + static const char hex[]="0123456789abcdef"; + int i; + for (i = 0; i < len; i++) { + buf[i<<1] = hex[digest[i] >> 4]; + buf[(i<<1) + 1] = hex[digest[i] & 0x0f]; + } + buf[i<<1] = '\0'; +} + static int lua_sha256_hexdigest(lua_State *L) { SHA256_CTX *ctx; - char buf[SHA256_DIGEST_LEN * 2 + 1]; unsigned char digest[SHA256_DIGEST_LEN]; - static const char hex[]="0123456789abcdef"; - int i; + char buf[SHA256_DIGEST_LEN * 2 + 1]; ctx = luaL_checkudata(L, 1, SHA256_META); + SHA256_Final(digest, ctx); - for (i = 0; i < SHA256_DIGEST_LEN; i++) { - buf[i+i] = hex[digest[i] >> 4]; - buf[i+i+1] = hex[digest[i] & 0x0f]; - } - buf[i+i] = '\0'; + hash_hexdigest(buf, digest, SHA256_DIGEST_LEN); lua_pushstring(L, buf); return (1); } +static int +lua_sha512_hexdigest(lua_State *L) +{ + SHA512_CTX *ctx; + unsigned char digest[SHA512_DIGEST_LEN]; + char buf[SHA512_DIGEST_LEN * 2 + 1]; + + ctx = luaL_checkudata(L, 1, SHA512_META); + + SHA512_Final(digest, ctx); + hash_hexdigest(buf, digest, SHA512_DIGEST_LEN); + + lua_pushstring(L, buf); + return (1); +} /* + * Zeros out the ctx before garbage collection. Normally this is done in * obj:digest or obj:hexdigest, but if not, it will be wiped here. Lua * manages freeing the ctx memory. @@ -100,9 +153,19 @@ return (0); } +static int +lua_sha512_done(lua_State *L) +{ + SHA512_CTX *ctx; + + ctx = luaL_checkudata(L, 1, SHA512_META); + memset(ctx, 0, sizeof(*ctx)); + + return (0); +} /* - * Create object obj which accumulates the state of the sha256 digest + * Create object obj which accumulates the state of the sha256/512 digest * for its contents and any subsequent obj:update call. It takes zero * or 1 arguments. */ @@ -132,9 +195,35 @@ return (1); // data . ctx } +static int +lua_sha512(lua_State *L) +{ + SHA512_CTX *ctx; + int top; + + /* We take 0 or 1 args */ + top = lua_gettop(L); // data -- data + if (top > 1) { + lua_pushnil(L); + return (1); + } + + ctx = lua_newuserdata(L, sizeof(*ctx)); // data -- data ctx + SHA512_Init(ctx); + if (top == 1) { + size_t len; + const unsigned char *data; + + data = luaL_checklstring(L, 1, &len); + SHA512_Update(ctx, data, len); + } + luaL_setmetatable(L, SHA512_META); // data ctx -- data ctx + + return (1); // data . ctx +} /* - * Setup the metatable to manage our userdata that we create in lua_sha256. We + * Setup the metatable to manage our userdata that we create in lua_sha256/512. We * request a finalization call with __gc so we can zero out the ctx buffer so * that we don't leak secrets if obj:digest or obj:hexdigest aren't called. */ @@ -158,10 +247,31 @@ lua_pop(L, 1); // meta -- } +static void +register_metatable_sha512(lua_State *L) +{ + luaL_newmetatable(L, SHA512_META); // -- meta + + lua_newtable(L); // meta -- meta tbl + lua_pushcfunction(L, lua_sha512_update); // meta tbl -- meta tbl fn + lua_setfield(L, -2, "update"); // meta tbl fn -- meta tbl + lua_pushcfunction(L, lua_sha512_digest); // meta tbl -- meta tbl fn + lua_setfield(L, -2, "digest"); // meta tbl fn -- meta tbl + lua_pushcfunction(L, lua_sha512_hexdigest); // meta tbl -- meta tbl fn + lua_setfield(L, -2, "hexdigest"); // meta tbl fn -- meta tbl + + /* Associate tbl with metatable */ + lua_setfield(L, -2, "__index"); // meta tbl -- meta + lua_pushcfunction(L, lua_sha512_done); // meta -- meta fn + lua_setfield(L, -2, "__gc"); // meta fn -- meta + + lua_pop(L, 1); // meta -- +} #define REG_SIMPLE(n) { #n, lua_ ## n } static const struct luaL_Reg hashlib[] = { REG_SIMPLE(sha256), + REG_SIMPLE(sha512), { NULL, NULL }, }; #undef REG_SIMPLE @@ -170,6 +280,7 @@ luaopen_hash(lua_State *L) { register_metatable_sha256(L); + register_metatable_sha512(L); luaL_newlib(L, hashlib); diff --git a/stand/common/interp_lua.c b/stand/common/interp_lua.c --- a/stand/common/interp_lua.c +++ b/stand/common/interp_lua.c @@ -93,6 +93,7 @@ {"lfs", luaopen_lfs}, {"loader", luaopen_loader}, {"pager", luaopen_pager}, + {"hash", luaopen_hash}, {NULL, NULL} }; diff --git a/stand/defaults/loader.conf.5 b/stand/defaults/loader.conf.5 --- a/stand/defaults/loader.conf.5 +++ b/stand/defaults/loader.conf.5 @@ -219,7 +219,29 @@ Protect boot menu with a password without interrupting .Ic autoboot process. -The password should be in clear text format. +.Pp +The password may be in clear text format or the +lua-loader additionally supports +.Xr crypt 3 +format with algorithms 5 (SHA-256) or 6 (SHA-512). +To use +.Xr crypt 3 +format passwords prefix the stored password with +.Dq Li crypt: +and escape any +.Dq Li \[Do] +with +.Dq Li \e . +.Xr crypt 3 +passwords may be generated with +.Xr openssl 1 +in +.Dq Li passwd +mode by specifying +.Dq Li -5 +or +.Dq Li -6 +for algorithm 5 or 6 passwords respectively. If a password is set, boot menu will not appear until any key is pressed during countdown period specified by .Va autoboot_delay @@ -231,7 +253,29 @@ .It Ar bootlock_password Provides a password to be required by check-password before execution is allowed to continue. -The password should be in clear text format. +.Pp +The password may be in clear text format or the +lua-loader additionally supports +.Xr crypt 3 +format with algorithms 5 (SHA-256) or 6 (SHA-512). +To use +.Xr crypt 3 +format passwords prefix the stored password with +.Dq Li crypt: +and escape any +.Dq Li \[Do] +with +.Dq Li \e . +.Xr crypt 3 +passwords may be generated with +.Xr openssl 1 +in +.Dq Li passwd +mode by specifying +.Dq Li -5 +or +.Dq Li -6 +for algorithm 5 or 6 passwords respectively. If a password is set, the user must provide specified password to boot. .It Ar verbose_loading If set to diff --git a/stand/liblua/lutils.h b/stand/liblua/lutils.h --- a/stand/liblua/lutils.h +++ b/stand/liblua/lutils.h @@ -30,6 +30,7 @@ int luaopen_loader(lua_State *); int luaopen_io(lua_State *); int luaopen_pager(lua_State *); +int luaopen_hash(lua_State *); #include diff --git a/stand/lua/Makefile b/stand/lua/Makefile --- a/stand/lua/Makefile +++ b/stand/lua/Makefile @@ -18,6 +18,7 @@ color.lua \ config.lua \ core.lua \ + crypt.lua \ drawer.lua \ hook.lua \ loader.lua \ diff --git a/stand/lua/crypt.lua b/stand/lua/crypt.lua new file mode 100644 --- /dev/null +++ b/stand/lua/crypt.lua @@ -0,0 +1,435 @@ +-- +-- SPDX-License-Identifier: BSD-2-Clause +-- +-- Copyright (c) 2024 David Cross +-- All rights reserved. +-- + +local b64_alphabet = {'.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', + '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', + 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', + 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', + 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', + 'v', 'w', 'x', 'y', 'z'} + +local shaX_rounds_prefix = "rounds=" +local shaX_rounds_default = 5000 +local shaX_rounds_min = 1000 +local shaX_rounds_max = 999999999 +local shaX_salt_max = 16 + +local function b64_from_24bit(b2, b1, b0, n) + local w = ((b2 & 0xff) << 16) | ((b1 & 0xff) << 8) | (b0 & 0xff) + local s = "" + for _ = 1, n do + s = s .. b64_alphabet[ (w & 0x3f) + 1] + w = w >> 6 + end + return s +end + +local function min(a, b) + if a < b then + return a + end + return b +end + +local function max(a, b) + if a > b then + return a + end + return b +end + +local crypt = {} + +function crypt.sha256(salthash, password) + local rounds = shaX_rounds_default + local custom_rounds=false + + if string.sub(salthash, 1, #shaX_rounds_prefix) == shaX_rounds_prefix then + local end_rounds = string.find(salthash, "%$") + if end_rounds ~= nil then + local new_rounds = tonumber(string.sub(salthash, #shaX_rounds_prefix + 1, end_rounds - 1)) + if new_rounds ~= nil then + rounds = max(shaX_rounds_min, min(new_rounds, shaX_rounds_max)); + custom_rounds=true + salthash = string.sub(salthash, end_rounds + 1) + end + end + end + local saltsplit = string.find(salthash, '%$') + if saltsplit == nil then + saltsplit = #salthash+1 + end + if saltsplit > (shaX_salt_max + 1) then + saltsplit = shaX_salt_max + 1 + end + local salt = string.sub(salthash, 1, saltsplit-1) + + local ctx = hash.sha256(password) + ctx:update(salt) + + local ctx_alt = hash.sha256(password) + ctx_alt:update(salt) + ctx_alt:update(password) + + local alt_result = ctx_alt:digest() + -- for each character of the password, add alt_result (wrapping as needed) + for _ = #password, 33, -32 do + ctx:update(alt_result) + end + ctx:update(string.sub(alt_result, 1, #password % 32)) + + local count = #password + while count > 0 do + if (count & 1) ~=0 then + ctx:update(alt_result) + else + ctx:update(password) + end + count = count >> 1 + end + + alt_result=ctx:digest() + + -- P sequence + ctx = hash.sha256() + for _ = 1, #password do + ctx:update(password) + end + local temp_result = ctx:digest() + local p_bytes = "" + + while #p_bytes ~= #password do + p_bytes = p_bytes .. string.sub(temp_result, 1, + min(#temp_result, #password-#p_bytes)) + end + + -- S sequence + ctx = hash.sha256() + for _ = 1, 16 + string.byte(alt_result, 1) do + ctx:update(salt) + end + temp_result = ctx:digest() + + local s_bytes = "" + while #s_bytes ~= #salt do + s_bytes = s_bytes .. string.sub(temp_result, 1, + min(#temp_result, #salt - #s_bytes)) + end + + for cnt=0, rounds-1 do + ctx = hash.sha256() + if (cnt & 1) ~= 0 then + ctx:update(p_bytes) + else + ctx:update(alt_result) + end + if (cnt % 3) ~= 0 then + ctx:update(s_bytes) + end + if (cnt % 7) ~= 0 then + ctx:update(p_bytes) + end + if (cnt & 1) ~= 0 then + ctx:update(alt_result) + else + ctx:update(p_bytes) + end + alt_result = ctx:digest() + end + local s = "$5$" + if custom_rounds then + s = s .. "rounds=" .. rounds .. "$" + end + + s = s .. salt .. "$" + s = s .. b64_from_24bit(string.byte(alt_result, 1), string.byte(alt_result, 11), string.byte(alt_result, 21), 4); + s = s .. b64_from_24bit(string.byte(alt_result, 22), string.byte(alt_result, 2), string.byte(alt_result, 12), 4); + s = s .. b64_from_24bit(string.byte(alt_result, 13), string.byte(alt_result, 23), string.byte(alt_result, 3), 4); + s = s .. b64_from_24bit(string.byte(alt_result, 4), string.byte(alt_result, 14), string.byte(alt_result, 24), 4); + s = s .. b64_from_24bit(string.byte(alt_result, 25), string.byte(alt_result, 5), string.byte(alt_result, 15), 4); + s = s .. b64_from_24bit(string.byte(alt_result, 16), string.byte(alt_result, 26), string.byte(alt_result, 6), 4); + s = s .. b64_from_24bit(string.byte(alt_result, 7), string.byte(alt_result, 17), string.byte(alt_result, 27), 4); + s = s .. b64_from_24bit(string.byte(alt_result, 28), string.byte(alt_result, 8), string.byte(alt_result, 18), 4); + s = s .. b64_from_24bit(string.byte(alt_result, 19), string.byte(alt_result, 29), string.byte(alt_result, 9), 4); + s = s .. b64_from_24bit(string.byte(alt_result, 10), string.byte(alt_result, 20), string.byte(alt_result, 30), 4); + s = s .. b64_from_24bit(0, string.byte(alt_result, 32), string.byte(alt_result, 31), 3); + + return s +end + +function crypt.sha512(salthash, password) + local rounds = shaX_rounds_default + local custom_rounds=false + if string.sub(salthash, 1, #shaX_rounds_prefix) == shaX_rounds_prefix then + local end_rounds = string.find(salthash, "%$") + if end_rounds ~= nil then + local new_rounds = tonumber(string.sub(salthash, #shaX_rounds_prefix + 1, end_rounds - 1)) + if new_rounds ~= nil then + rounds = max(shaX_rounds_min, min(new_rounds, shaX_rounds_max)); + custom_rounds=true + salthash = string.sub(salthash, end_rounds + 1) + end + end + end + local saltsplit = string.find(salthash, '%$') + if saltsplit == nil then + saltsplit = #salthash+1 + end + if saltsplit > (shaX_salt_max + 1) then + saltsplit = shaX_salt_max + 1 + end + local salt = string.sub(salthash, 1, saltsplit-1) + + local ctx = hash.sha512(password) + ctx:update(salt) + + local ctx_alt = hash.sha512(password) + ctx_alt:update(salt) + ctx_alt:update(password) + + local alt_result = ctx_alt:digest() + -- for each character of the password add the alt_result (wrapping as needed) + for _ = #password, 65, -64 do + ctx:update(alt_result) + end + ctx:update(string.sub(alt_result, 1, #password % 64)) + + local count = #password + while count > 0 do + if (count & 1) ~=0 then + ctx:update(alt_result) + else + ctx:update(password) + end + count = count >> 1 + end + + -- P sequence + alt_result=ctx:digest() + ctx = hash.sha512() + for _ = 1, #password do + ctx:update(password) + end + local temp_result = ctx:digest() + local p_bytes = "" + + while #p_bytes ~= #password do + p_bytes = p_bytes .. string.sub(temp_result, 1, + min(#temp_result, #password-#p_bytes)) + end + + -- S sequence + ctx = hash.sha512() + for _ = 1, 16 + string.byte(alt_result, 1) do + ctx:update(salt) + end + temp_result = ctx:digest() + + local s_bytes = "" + while #s_bytes ~= #salt do + s_bytes = s_bytes .. string.sub(temp_result, 1, + min(#temp_result, #salt - #s_bytes)) + end + + for cnt=0, rounds-1 do + ctx = hash.sha512() + if (cnt & 1) ~= 0 then + ctx:update(p_bytes) + else + ctx:update(alt_result) + end + if (cnt % 3) ~= 0 then + ctx:update(s_bytes) + end + if (cnt % 7) ~= 0 then + ctx:update(p_bytes) + end + if (cnt & 1) ~= 0 then + ctx:update(alt_result) + else + ctx:update(p_bytes) + end + alt_result = ctx:digest() + end + local s = "$6$" + if custom_rounds then + s = s .. "rounds=" .. rounds .. "$" + end + s = s .. salt .. "$" + s = s .. b64_from_24bit(string.byte(alt_result, 1), string.byte(alt_result, 22), + string.byte(alt_result, 43), 4); + s = s .. b64_from_24bit(string.byte(alt_result, 23), string.byte(alt_result, 44), + string.byte(alt_result, 2), 4); + s = s .. b64_from_24bit(string.byte(alt_result, 45), string.byte(alt_result, 3), + string.byte(alt_result, 24), 4); + s = s .. b64_from_24bit(string.byte(alt_result, 4), string.byte(alt_result, 25), + string.byte(alt_result, 46), 4); + s = s .. b64_from_24bit(string.byte(alt_result, 26), string.byte(alt_result, 47), + string.byte(alt_result, 5), 4); + s = s .. b64_from_24bit(string.byte(alt_result, 48), string.byte(alt_result, 6), + string.byte(alt_result, 27), 4); + s = s .. b64_from_24bit(string.byte(alt_result, 7), string.byte(alt_result, 28), + string.byte(alt_result, 49), 4); + s = s .. b64_from_24bit(string.byte(alt_result, 29), string.byte(alt_result, 50), + string.byte(alt_result, 8), 4); + s = s .. b64_from_24bit(string.byte(alt_result, 51), string.byte(alt_result, 9), + string.byte(alt_result, 30), 4); + s = s .. b64_from_24bit(string.byte(alt_result, 10), string.byte(alt_result, 31), + string.byte(alt_result, 52), 4); + s = s .. b64_from_24bit(string.byte(alt_result, 32), string.byte(alt_result, 53), + string.byte(alt_result, 11), 4); + s = s .. b64_from_24bit(string.byte(alt_result, 54), string.byte(alt_result, 12), + string.byte(alt_result, 33), 4); + s = s .. b64_from_24bit(string.byte(alt_result, 13), string.byte(alt_result, 34), + string.byte(alt_result, 55), 4); + s = s .. b64_from_24bit(string.byte(alt_result, 35), string.byte(alt_result, 56), + string.byte(alt_result, 14), 4); + s = s .. b64_from_24bit(string.byte(alt_result, 57), string.byte(alt_result, 15), + string.byte(alt_result, 36), 4); + s = s .. b64_from_24bit(string.byte(alt_result, 16), string.byte(alt_result, 37), + string.byte(alt_result, 58), 4); + s = s .. b64_from_24bit(string.byte(alt_result, 38), string.byte(alt_result, 59), + string.byte(alt_result, 17), 4); + s = s .. b64_from_24bit(string.byte(alt_result, 60), string.byte(alt_result, 18), + string.byte(alt_result, 39), 4); + s = s .. b64_from_24bit(string.byte(alt_result, 19), string.byte(alt_result, 40), + string.byte(alt_result, 61), 4); + s = s .. b64_from_24bit(string.byte(alt_result, 41), string.byte(alt_result, 62), + string.byte(alt_result, 20), 4); + s = s .. b64_from_24bit(string.byte(alt_result, 63), string.byte(alt_result, 21), + string.byte(alt_result, 42), 4); + s = s .. b64_from_24bit(0, 0, string.byte(alt_result, 64), 2); + + return s +end + +function crypt.compare(entered, stored) + local algorithm = string.sub(stored, 1, 3) + local salthash = string.sub(stored, 4) + local hashed + if algorithm == '$5$' then hashed = crypt.sha256(salthash, entered) + elseif algorithm == '$6$' then hashed = crypt.sha512(salthash, entered) + end + if hashed ~= nil then + return hashed == stored + end + return nil +end + +-- luacheck: push no max line length +-- hash tests +-- warning: these tests take about 24 minutes to run +--[[ +local tests_hash = { + { string = "abc", + iterations = 1, + expected = { + sha256 = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", + sha512 = "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f" } + } , + { string = "", + iterations = 1, + expected = { + sha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + sha512 = "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e" } + }, + { string = "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + iterations = 1, + expected = { + sha256 = "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1", + sha512 = "204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c33596fd15c13b1b07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445" } + } , + { string = "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu", + iterations = 1, + expected = { + sha256 = "cf5b16a778af8380036ce59e7b0492370b249b11e8f07a51afac45037afee9d1", + sha512 = "8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299aeadb6889018501d289e4900f7e4331b99dec4b5433ac7d329eeb6dd26545e96e55b874be909" } + }, + { string = "a", + iterations = 1000000, + expected = { + sha256 = "cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0", + sha512 = "e718483d0ce769644e2e42c7bc15b4638e1f98b13b2044285632a803afa973ebde0ff244877ea60a4cb0432ce577c31beb009c5c2c49aa2e4eadb217ad8cc09b" } + }, + { string = "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmno", + iterations = 16777216, + expected = { + sha256 = "50e72a0e26442fe2552dc3938ac58658228c0cbfb1d2ca872ae435266fcd055e", + sha512 = "b47c933421ea2db149ad6e10fce6c7f93d0752380180ffd7f4629a712134831d77be6091b819ed352c2967a2e2d4fa5050723c9630691f1a05a7281dbe6c1086" } + } +} + +print("WARNING: you have enabled hash validation tests") +for test = 1, #tests_hash do + local sha512_ctx=hash.sha512() + local sha256_ctx=hash.sha256() + local string = tests_hash[test].string + local expected = tests_hash[test].expected + local iterations = tests_hash[test].iterations + for _ = 1, iterations do + sha256_ctx:update(string) + sha512_ctx:update(string) + end + local sha256_digest = sha256_ctx:hexdigest() + local sha512_digest = sha512_ctx:hexdigest() + assert(sha256_digest == expected.sha256) + assert(sha512_digest == expected.sha512) +end +--]] + +-- crypt(3) tests +-- warning: These tests take about 1 minute to run +--[[ +local tests_crypt = { + { salt = "saltstring", + string = "Hello world!", + expected = { + sha512 = "$6$saltstring$svn8UoSVapNtMuq1ukKS4tPQd8iKwSMHWjl/O817G3uBnIFNjnQJuesI68u4OTLiBFdcbYEdFCoEOfaS35inz1", + sha256 = "$5$saltstring$5B8vYYiY.CVt1RlTTf8KbXBH3hsxY/GNooZaBBGWEc5"}}, + { salt = "rounds=10000$saltstringsaltstring", + string = "Hello world!", + expected = { + sha512 = "$6$rounds=10000$saltstringsaltst$OW1/O6BYHV6BcXZu8QVeXbDWra3Oeqh0sbHbbMCVNSnCM/UrjmM0Dp8vOuZeHBy/YTBmSK6H9qs/y3RnOaw5v.", + sha256 = "$5$rounds=10000$saltstringsaltst$3xv.VbSHBb41AL9AvLeujZkZRBAwqFMz2.opqey6IcA"}}, + { salt = "rounds=5000$toolongsaltstring", + string = "This is just a test", + expected = { + sha512 = "$6$rounds=5000$toolongsaltstrin$lQ8jolhgVRVhY4b5pZKaysCLi0QBxGoNeKQzQ3glMhwllF7oGDZxUhx1yxdYcz/e1JSbq3y6JMxxl8audkUEm0", + sha256 = "$5$rounds=5000$toolongsaltstrin$Un/5jzAHMgOGZ5.mWJpuVolil07guHPvOW8mGRcvxa5"}}, + { salt = "rounds=1400$anotherlongsaltstring", + string = "a very much longer text to encrypt. This one even stretches over morethan one line.", + expected = { + sha512 = "$6$rounds=1400$anotherlongsalts$POfYwTEok97VWcjxIiSOjiykti.o/pQs.wPvMxQ6Fm7I6IoYN3CmLs66x9t0oSwbtEW7o7UmJEiDwGqd8p4ur1", + sha256 = "$5$rounds=1400$anotherlongsalts$Rx.j8H.h8HjEDGomFU8bDkXm3XIUnzyxf12oP84Bnq1" }}, + { salt = "rounds=77777$short", + string = "we have a short salt string but not a short password", + expected = { + sha512 = "$6$rounds=77777$short$WuQyW2YR.hBNpjjRhpYD/ifIw05xdfeEyQoMxIXbkvr0gge1a1x3yRULJ5CCaUeOxFmtlcGZelFl5CxtgfiAc0", + sha256 = "$5$rounds=77777$short$JiO1O3ZpDAxGJeaDIuqCoEFysAe1mZNJRs3pw0KQRd/" }}, + { salt = "rounds=123456$asaltof16chars..", + string = "a short string", + expected = { + sha512 = "$6$rounds=123456$asaltof16chars..$BtCwjqMJGx5hrJhZywWvt0RLE8uZ4oPwcelCjmw2kSYu.Ec6ycULevoBK25fs2xXgMNrCzIMVcgEJAstJeonj1", + sha256 = "$5$rounds=123456$asaltof16chars..$gP3VQ/6X7UUEW3HkBn2w1/Ptq2jxPyzV/cZKmF/wJvD" }}, + { salt = "rounds=10$roundstoolow", + string = "the minimum number is still observed", + expected = { + sha512 = "$6$rounds=1000$roundstoolow$kUMsbe306n21p9R.FRkW3IGn.S9NPN0x50YhH1xhLsPuWGsUSklZt58jaTfF4ZEQpyUNGc0dqbpBYYBaHHrsX.", + sha256 = "$5$rounds=1000$roundstoolow$yfvwcWrQ8l/K0DAWyuPMDNHpIVlTQebY9l/gL972bIC" }} +} + +print("WARNING: you have enabled crypt(3) validation tests") +for test = 1, #tests_crypt do + local salt = tests_crypt[test].salt + local string = tests_crypt[test].string + local expected = tests_crypt[test].expected + assert(crypt.sha256(salt, string) == expected.sha256) + assert(crypt.sha512(salt, string) == expected.sha512) +end +--]] +-- luacheck: pop +return crypt diff --git a/stand/lua/password.lua b/stand/lua/password.lua --- a/stand/lua/password.lua +++ b/stand/lua/password.lua @@ -29,6 +29,7 @@ local core = require("core") local screen = require("screen") +local crypt = require("crypt") local password = {} @@ -85,6 +86,19 @@ end function password.check() + local function compare(entered, stored) + if stored == nil then + return true + end + local result + if string.sub(stored, 1, 6) == "crypt:" then + result = crypt.compare(entered, string.sub(stored, 7)) + end + if result == nil then + return entered == stored + end + return result + end -- pwd is optionally supplied if we want to check it local function doPrompt(prompt, pwd) local attempts = 1 @@ -104,7 +118,7 @@ screen.defcursor() printc(prompt) local read_pwd = password.read(#prompt) - if pwd == nil or pwd == read_pwd then + if compare(read_pwd, pwd) then -- Clear the prompt + twiddle printc(string.rep(" ", #prompt + 5)) return read_pwd @@ -114,7 +128,7 @@ loader.delay(3*1000*1000) end end - local function compare(prompt, pwd) + local function pwGate(prompt, pwd) if pwd == nil then return end @@ -122,7 +136,7 @@ end local boot_pwd = loader.getenv("bootlock_password") - compare("Bootlock password:", boot_pwd) + pwGate("Bootlock password:", boot_pwd) local geli_prompt = loader.getenv("geom_eli_passphrase_prompt") if geli_prompt ~= nil and geli_prompt:lower() == "yes" then @@ -140,7 +154,7 @@ -- in the middle of other text. setup_screen() end - compare("Loader password:", pwd) + pwGate("Loader password:", pwd) end return password