fediauth/functions.lua

235 lines
6.2 KiB
Lua
Raw Normal View History

2023-01-28 11:04:14 +00:00
-- https://stackoverflow.com/a/25594410
2023-01-28 16:25:39 +00:00
local function bitxor(a,b)
2023-01-28 11:04:14 +00:00
local p,c=1,0
while a>0 and b>0 do
local ra,rb=a%2,b%2
if ra~=rb then c=c+p end
a,b,p=(a-ra)/2,(b-rb)/2,p*2
end
if a<b then a=b end
while a>0 do
local ra=a%2
if ra>0 then c=c+p end
a,p=(a-ra)/2,p*2
end
return c
end
2023-01-28 16:25:39 +00:00
local function bitor(a,b)
local p,c=1,0
while a+b>0 do
local ra,rb=a%2,b%2
if ra+rb>0 then c=c+p end
a,b,p=(a-ra)/2,(b-rb)/2,p*2
end
return c
end
2023-01-28 11:04:14 +00:00
-- https://stackoverflow.com/a/32387452
local function bitand(a, b)
local result = 0
local bitval = 1
while a > 0 and b > 0 do
if a % 2 == 1 and b % 2 == 1 then -- test the rightmost bits
result = result + bitval -- set the current bit
end
bitval = bitval * 2 -- shift left
a = math.floor(a/2) -- shift right
b = math.floor(b/2)
end
return result
end
-- https://gist.github.com/mebens/938502
local function rshift(x, by)
return math.floor(x / 2 ^ by)
end
2023-01-28 16:25:39 +00:00
local function lshift(x, by)
return x * 2 ^ by
end
-- big-endian uint64 of a number
2023-09-27 10:57:15 +00:00
function fediauth.write_uint64_be(v)
2023-01-28 16:25:39 +00:00
local b1 = bitand( rshift(v, 56), 0xFF )
local b2 = bitand( rshift(v, 48), 0xFF )
local b3 = bitand( rshift(v, 40), 0xFF )
local b4 = bitand( rshift(v, 32), 0xFF )
local b5 = bitand( rshift(v, 24), 0xFF )
local b6 = bitand( rshift(v, 16), 0xFF )
local b7 = bitand( rshift(v, 8), 0xFF )
local b8 = bitand( rshift(v, 0), 0xFF )
2023-01-28 11:04:14 +00:00
return string.char(b1, b2, b3, b4, b5, b6, b7, b8)
end
-- prepare paddings
-- https://en.wikipedia.org/wiki/HMAC
local i_pad = ""
local o_pad = ""
for _=1,64 do
i_pad = i_pad .. string.char(0x36)
o_pad = o_pad .. string.char(0x5c)
end
-- hmac generation
2023-09-27 10:57:15 +00:00
function fediauth.hmac(key, message)
2023-01-28 11:04:14 +00:00
local i_key_pad = ""
for i=1,64 do
2023-01-28 16:25:39 +00:00
i_key_pad = i_key_pad .. string.char(bitxor(string.byte(key, i) or 0x00, string.byte(i_pad, i)))
2023-01-28 11:04:14 +00:00
end
2023-01-28 16:25:39 +00:00
assert(#i_key_pad == 64)
2023-01-28 11:04:14 +00:00
local o_key_pad = ""
for i=1,64 do
2023-01-28 16:25:39 +00:00
o_key_pad = o_key_pad .. string.char(bitxor(string.byte(key, i) or 0x00, string.byte(o_pad, i)))
2023-01-28 11:04:14 +00:00
end
2023-01-28 16:25:39 +00:00
assert(#o_key_pad == 64)
2023-01-28 11:04:14 +00:00
-- concat message
local first_msg = i_key_pad
for i=1,#message do
2023-01-28 16:25:39 +00:00
first_msg = first_msg .. string.char(string.byte(message, i))
2023-01-28 11:04:14 +00:00
end
2023-01-28 16:25:39 +00:00
assert(#first_msg == 64+8)
2023-01-28 11:04:14 +00:00
-- hash first message
local hash_sum_1 = minetest.sha1(first_msg, true)
2023-01-28 16:25:39 +00:00
assert(#hash_sum_1 == 20)
2023-01-28 11:04:14 +00:00
2023-01-28 16:26:35 +00:00
-- concat first message to second
2023-01-28 11:04:14 +00:00
local second_msg = o_key_pad
for i=1,#hash_sum_1 do
2023-01-28 16:25:39 +00:00
second_msg = second_msg .. string.char(string.byte(hash_sum_1, i))
2023-01-28 11:04:14 +00:00
end
2023-01-28 16:25:39 +00:00
assert(#second_msg == 64+20)
local hmac = minetest.sha1(second_msg, true)
assert(#hmac == 20)
return hmac
end
2023-01-28 18:37:27 +00:00
local function left_pad(str, s, len)
while #str < len do
str = s .. str
end
return str
end
2023-09-27 10:57:15 +00:00
function fediauth.generate_tfediauth(secret_b32, unix_time)
local key = fediauth.basexx.from_base32(secret_b32)
2023-01-28 18:37:27 +00:00
unix_time = unix_time or os.time()
local tx = 30
local ct = math.floor(unix_time / tx)
2023-09-27 10:57:15 +00:00
local counter = fediauth.write_uint64_be(ct)
2023-01-28 18:37:27 +00:00
local valid_seconds = ((ct * tx) + tx) - unix_time
2023-09-27 10:57:15 +00:00
local hmac = fediauth.hmac(key, counter)
2023-01-28 11:04:14 +00:00
2023-01-28 16:25:39 +00:00
-- https://www.rfc-editor.org/rfc/rfc4226#section-5.4
local offset = bitand(string.byte(hmac, #hmac), 0xF)
local value = 0
value = bitor(value, string.byte(hmac, offset+4))
value = bitor(value, lshift(string.byte(hmac, offset+3), 8))
value = bitor(value, lshift(string.byte(hmac, offset+2), 16))
value = bitor(value, lshift(bitand(string.byte(hmac, offset+1), 0x7F), 24))
2023-01-28 18:37:27 +00:00
local code = value % 10^6
local padded_code = left_pad("" .. code, "0", 6)
return padded_code, valid_seconds
end
2023-09-27 10:57:15 +00:00
function fediauth.create_qr_png(data)
2023-01-29 12:26:24 +00:00
local height = #data + 2
2023-01-28 18:37:27 +00:00
local width = height
local png_data = {}
2023-01-29 12:26:24 +00:00
-- top padding
for _=1,width do
table.insert(png_data, 0xFFFFFFFF)
end
2023-01-28 18:37:27 +00:00
for _, row in ipairs(data) do
2023-01-29 12:26:24 +00:00
-- left padding
table.insert(png_data, 0xFFFFFFFF)
2023-01-28 18:55:46 +00:00
for _, v in ipairs(row) do
if v > 0 then
table.insert(png_data, 0xFF000000)
else
table.insert(png_data, 0xFFFFFFFF)
2023-01-28 18:37:27 +00:00
end
end
2023-01-29 12:26:24 +00:00
-- right padding
table.insert(png_data, 0xFFFFFFFF)
2023-01-28 18:37:27 +00:00
end
2023-01-29 12:26:24 +00:00
-- bottom padding
for _=1,width do
table.insert(png_data, 0xFFFFFFFF)
end
assert(#png_data == width*height)
2023-01-28 18:37:27 +00:00
return minetest.encode_png(width, height, png_data, 2)
2023-01-28 18:47:01 +00:00
end
2023-09-27 10:57:15 +00:00
function fediauth.generate_secret()
2023-01-28 18:47:01 +00:00
local buf = minetest.sha1("" .. math.random(10000), true)
local s = ""
for i=1,20 do
s = s .. string.char(string.byte(buf, i))
end
return s
2023-01-29 11:13:50 +00:00
end
-- get or generate per-player secret b32 ecoded
2023-09-27 10:57:15 +00:00
function fediauth.get_player_secret_b32(name)
local secret_b32 = fediauth.storage:get_string(name .. "_secret")
2023-01-29 11:13:50 +00:00
if secret_b32 == "" then
2023-09-27 10:57:15 +00:00
secret_b32 = fediauth.basexx.to_base32(fediauth.generate_secret())
fediauth.storage:set_string(name .. "_secret", secret_b32)
2023-01-29 11:13:50 +00:00
end
return secret_b32
end
2023-09-27 10:57:15 +00:00
-- returns true if the player is fediauth enabled _and_ set up properly
function fediauth.is_player_enabled(name)
local has_secret = fediauth.storage:get_string(name .. "_secret") ~= ""
local has_priv = minetest.check_player_privs(name, "fediauth_enabled")
return has_secret and has_priv
2023-02-08 20:40:47 +00:00
end
2023-09-27 10:57:15 +00:00
function fediauth.is_player_bypassed(name)
local has_priv = minetest.check_player_privs(name, "fediauth_bypass")
return has_priv
end
function fediauth.check_code(secret_b32, code, time)
2023-02-08 20:40:47 +00:00
time = time or os.time()
2023-09-27 10:57:15 +00:00
for _, t_offset in ipairs({0, -30, 30, -60, 60}) do
local expected_code = fediauth.generate_tfediauth(secret_b32, time + t_offset)
2023-02-08 20:40:47 +00:00
if expected_code == code then
return true
end
end
return false
2023-09-27 10:57:15 +00:00
end
function fediauth.give_code(secret_b32, time)
time = time or os.time()
local codeseq = {}
for _, t_offset in ipairs({60, 30, 0, -30, -60}) do
local expected_code = fediauth.generate_tfediauth(secret_b32, time + t_offset)
table.insert(codeseq, expected_code)
end
return codeseq
end
function fediauth.str_repeats(s,c)
local _,n = s:gsub(c,"")
return n
end