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 @@ -178,7 +178,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 password +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 @@ -190,7 +212,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 password +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/lua/Makefile b/stand/lua/Makefile --- a/stand/lua/Makefile +++ b/stand/lua/Makefile @@ -17,6 +17,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,696 @@ +-- +-- SPDX-License-Identifier: BSD-2-Clause-FreeBSD +-- +-- Copyright (c) 2024 David Cross +-- +-- Redistribution and use in source and binary forms, with or without +-- modification, are permitted provided that the following conditions +-- are met: +-- 1. Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- 2. Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- +-- THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +-- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +-- ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +-- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +-- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +-- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +-- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +-- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +-- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +-- SUCH DAMAGE. +-- +-- $FreeBSD$ +-- + +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 sha256_k = {0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, + 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, + 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, + 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, + 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, + 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, + 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, + 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, + 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, + 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 } + + +local sha512_k = {0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, + 0xe9b5dba58189dbbc, 0x3956c25bf348b538, 0x59f111f1b605d019, + 0x923f82a4af194f9b, 0xab1c5ed5da6d8118, 0xd807aa98a3030242, + 0x12835b0145706fbe, 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2, + 0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235, + 0xc19bf174cf692694, 0xe49b69c19ef14ad2, 0xefbe4786384f25e3, + 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65, 0x2de92c6f592b0275, + 0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5, + 0x983e5152ee66dfab, 0xa831c66d2db43210, 0xb00327c898fb213f, + 0xbf597fc7beef0ee4, 0xc6e00bf33da88fc2, 0xd5a79147930aa725, + 0x06ca6351e003826f, 0x142929670a0e6e70, 0x27b70a8546d22ffc, + 0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed, 0x53380d139d95b3df, + 0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6, + 0x92722c851482353b, 0xa2bfe8a14cf10364, 0xa81a664bbc423001, + 0xc24b8b70d0f89791, 0xc76c51a30654be30, 0xd192e819d6ef5218, + 0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8, + 0x19a4c116b8d2d0c8, 0x1e376c085141ab53, 0x2748774cdf8eeb99, + 0x34b0bcb5e19b48a8, 0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, + 0x5b9cca4f7763e373, 0x682e6ff3d6b2b8a3, 0x748f82ee5defb2fc, + 0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec, + 0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915, + 0xc67178f2e372532b, 0xca273eceea26619c, 0xd186b8c721c0c207, + 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178, 0x06f067aa72176fba, + 0x0a637dc5a2c898a6, 0x113f9804bef90dae, 0x1b710b35131c471b, + 0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc, + 0x431d67c49c100d4c, 0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, + 0x5fcb6fab3ad6faec, 0x6c44198c4a475817 } + +local function maskn(size) + return ~(~0 << size) +end + +local function ror(n, bits, size) + return (n >> bits) | ((n << size - bits) & maskn(size)) +end + +local function string2number(s, i, len) + local n = 0 + for x = i, i + len - 1 do + n = (n<<8) + string.byte(s, x) + end + return n +end + +local function sha512_block(H, chunk) + local w = {} + for i = 1, 16 do + w[i] = string2number(chunk, ((i-1) * 8)+1, 8) + end + + for i = 17, 80 do + local v = w[i - 15] + local s0 = ror(v, 1, 64) ~ ror(v, 8, 64) ~ (v >> 7) + v = w[i - 2] + local s1 = ror(v, 19, 64) ~ ror(v, 61, 64) ~ (v >> 6) + w[i] = (w[i - 16] + s0 + w[i - 7] + s1) & maskn(64) + end + local a, b, c, d, e, f, g, h = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] + + for i=1, 80 do + local s0 = ror(a, 28, 64) ~ ror(a, 34, 64) ~ ror(a, 39, 64) + local maj = (a & b) ~ (a & c) ~ (b & c) + local t2 = s0 + maj + local s1 = ror(e, 14, 64) ~ ror(e, 18, 64) ~ ror(e, 41, 64) + local ch = (e & f) ~ (~e & g) + local t1 = h + s1 + ch + sha512_k[i] + w[i] + + h = g + g = f + f = e + e = (d + t1) & maskn(64) + d = c + c = b + b = a + a = (t1 + t2) & maskn(64) + end + H[1] = maskn(64) & (H[1] + a) + H[2] = maskn(64) & (H[2] + b) + H[3] = maskn(64) & (H[3] + c) + H[4] = maskn(64) & (H[4] + d) + H[5] = maskn(64) & (H[5] + e) + H[6] = maskn(64) & (H[6] + f) + H[7] = maskn(64) & (H[7] + g) + H[8] = maskn(64) & (H[8] + h) +end + +local function sha256_block(H, chunk) + local w = {} + for i = 1, 16 do + w[i] = string2number(chunk, ((i-1) * 4)+1, 4) + end + + for i = 17, 64 do + local v = w[i - 15] + local s0 = ror(v, 7, 32) ~ ror(v, 18, 32) ~ (v >> 3) + v = w[i - 2] + local s1 = ror(v, 17, 32) ~ ror(v, 19, 32) ~ (v >> 10) + w[i] = (w[i - 16] + s0 + w[i - 7] + s1) & maskn(32) + end + local a, b, c, d, e, f, g, h = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] + + for i=1, 64 do + local s0 = ror(a, 2, 32) ~ ror(a, 13, 32) ~ ror(a, 22, 32) + local maj = (a & b) ~ (a & c) ~ (b & c) + local t2 = s0 + maj + local s1 = ror(e, 6, 32) ~ ror(e, 11, 32) ~ ror(e, 25, 32) + local ch = (e & f) ~ (~e & g) + local t1 = h + s1 + ch + sha256_k[i] + w[i] + + h = g + g = f + f = e + e = (d + t1) & maskn(32) + d = c + c = b + b = a + a = (t1 + t2) & maskn(32) + end + H[1] = maskn(32) & (H[1] + a) + H[2] = maskn(32) & (H[2] + b) + H[3] = maskn(32) & (H[3] + c) + H[4] = maskn(32) & (H[4] + d) + H[5] = maskn(32) & (H[5] + e) + H[6] = maskn(32) & (H[6] + f) + H[7] = maskn(32) & (H[7] + g) + H[8] = maskn(32) & (H[8] + h) +end + +local function number2string(n, length) + local s = "" + for _ = 1, length do + local rem = n % 256 + s = string.char(rem) .. s + n = n >> 8 + end + return s +end + +-- +-- For use by crypt routines only +-- do not re-export without further discussion +-- +local hashes = { + sha256 = function (message) + local buffer = "" + local length = 0 + local H = {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, + 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 } + local function update(msg) + if msg then + local endrange = 64 - #buffer + length = length + #msg + buffer = buffer..string.sub(msg, 1, endrange) + local offset = endrange + 1 + while (#buffer == 64) do + sha256_block(H, buffer) + buffer = string.sub(msg, offset, offset + 63) + offset = offset + 64 + end + return update + else + local padding = -(length + 1 + 8) % 64 + update("\128" .. string.rep("\0", padding) .. number2string(length*8, 8)) + buffer = nil + local result = "" + for i = 1, 8 do + result = result .. number2string(H[i], 4) + end + return result; + end + end + if message then + return update(message)() + else + return update + end + end, + + sha512 = function (message) + local length = 0 + local buffer = "" + local H = {0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b, + 0xa54ff53a5f1d36f1, 0x510e527fade682d1, 0x9b05688c2b3e6c1f, + 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179} + local function update(msg) + if msg then + local endrange = 128 - #buffer + length = length + #msg + buffer = buffer..string.sub(msg, 1, endrange) + local offset = endrange + 1 + while (#buffer == 128) do + sha512_block(H, buffer) + buffer = string.sub(msg, offset, offset + 127) + offset = offset + 128 + end + return update + else + local padding = (-(length + 1 + 16) % 128) + 8 + local padstring = "\128" .. string.rep("\0", padding) .. number2string(length*8, 8) + update(padstring) + buffer = nil + local result = "" + for i = 1, 8 do + result = result .. number2string(H[i], 8) + end + return result + end + end + if message then + return update(message)() + else + return update + end + end, + + string2hex = function (s) + local h = string.gsub(s, ".", function(c) return string.format("%02x", string.byte(c)) + end) + return h + end +} + +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 sha256 = hashes.sha256() + sha256(password) + sha256(salt) + + local sha256_alt = hashes.sha256() + sha256_alt(password) + sha256_alt(salt) + sha256_alt(password) + + local alt_result = sha256_alt() + -- for each character of the password, add alt_result (wrapping as needed) + for _ = #password, 33, -32 do + sha256(alt_result) + end + sha256(string.sub(alt_result, 1, #password % 32)) + + local count = #password + while count > 0 do + if (count & 1) ~=0 then + sha256(alt_result) + else + sha256(password) + end + count = count >> 1 + end + + alt_result=sha256() + + -- P sequence + sha256 = hashes.sha256() + for _ = 1, #password do + sha256(password) + end + local temp_result = sha256() + 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 + sha256 = hashes.sha256() + for _ = 1, 16 + string.byte(alt_result, 1) do + sha256(salt) + end + temp_result = sha256() + + 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 + sha256 = hashes.sha256() + if (cnt & 1) ~= 0 then + sha256(p_bytes) + else + sha256(alt_result) + end + if (cnt % 3) ~= 0 then + sha256(s_bytes) + end + if (cnt % 7) ~= 0 then + sha256(p_bytes) + end + if (cnt & 1) ~= 0 then + sha256(alt_result) + else + sha256(p_bytes) + end + alt_result = sha256() + 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 sha512 = hashes.sha512() + + sha512(password) + + sha512(salt) + + local alt_sha512 = hashes.sha512() + alt_sha512(password) + alt_sha512(salt) + alt_sha512(password) + local alt_result = alt_sha512() + -- for each character of the password add the alt_result (wrapping as needed) + for _ = #password, 65, -64 do + sha512(alt_result) + end + sha512(string.sub(alt_result, 1, #password % 64)) + + local count = #password + while count > 0 do + if (count & 1) ~=0 then + sha512(alt_result) + else + sha512(password) + end + count = count >> 1 + end + + -- P sequence + alt_result=sha512() + sha512 = hashes.sha512() + for _ = 1, #password do + sha512(password) + end + local temp_result = sha512() + 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 + sha512 = hashes.sha512() + for _ = 1, 16 + string.byte(alt_result, 1) do + sha512(salt) + end + temp_result = sha512() + + 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 + sha512 = hashes.sha512() + if (cnt & 1) ~= 0 then + sha512(p_bytes) + else + sha512(alt_result) + end + if (cnt % 3) ~= 0 then + sha512(s_bytes) + end + if (cnt % 7) ~= 0 then + sha512(p_bytes) + end + if (cnt & 1) ~= 0 then + sha512(alt_result) + else + sha512(p_bytes) + end + alt_result = sha512() + 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 sha512test=hashes.sha512() + local sha256test=hashes.sha256() + local string = tests_hash[test].string + local expected = tests_hash[test].expected + local iterations = tests_hash[test].iterations + for _ = 1, iterations do + sha256test(string) + sha512test(string) + end + assert(hashes.string2hex(sha256test()) == expected.sha256) + assert(hashes.string2hex(sha512test()) == 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 @@ -31,6 +31,7 @@ local core = require("core") local screen = require("screen") +local crypt = require("crypt") local password = {} @@ -87,6 +88,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 @@ -106,7 +120,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 @@ -116,7 +130,7 @@ loader.delay(3*1000*1000) end end - local function compare(prompt, pwd) + local function pwGate(prompt, pwd) if pwd == nil then return end @@ -124,7 +138,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 @@ -141,7 +155,7 @@ -- in the middle of other text. setup_screen() end - compare("Loader password:", pwd) + pwGate("Loader password:", pwd) end return password