diff --git a/functions.lua b/functions.lua index ed0abc1..db3f077 100644 --- a/functions.lua +++ b/functions.lua @@ -110,8 +110,22 @@ function otp.hmac(key, message) return hmac end -function otp.generate_code(key, message) - local hmac = otp.hmac(key, message) +local function left_pad(str, s, len) + while #str < len do + str = s .. str + end + return str +end + +function otp.generate_totp(key, unix_time) + unix_time = unix_time or os.time() + + local tx = 30 + local ct = math.floor(unix_time / tx) + local counter = otp.write_uint64_be(ct) + local valid_seconds = ((ct * tx) + tx) - unix_time + + local hmac = otp.hmac(key, counter) -- https://www.rfc-editor.org/rfc/rfc4226#section-5.4 local offset = bitand(string.byte(hmac, #hmac), 0xF) @@ -120,5 +134,33 @@ function otp.generate_code(key, message) 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)) - return value % 10^6 + local code = value % 10^6 + local padded_code = left_pad("" .. code, "0", 6) + + return padded_code, valid_seconds +end + +function otp.create_qr_png(data) + local pixel_size = 3 + local height = pixel_size * #data + local width = height + + local png_data = {} + for _, row in ipairs(data) do + assert(#row == #data) + for _=1,pixel_size do + for _, v in ipairs(row) do + for _=1,pixel_size do + if v > 0 then + table.insert(png_data, 0xFF000000) + else + table.insert(png_data, 0xFFFFFFFF) + end + end + end + end + end + assert(#png_data == width*height) + + return minetest.encode_png(width, height, png_data, 2) end \ No newline at end of file diff --git a/functions.spec.lua b/functions.spec.lua index f53a39d..e5d629e 100644 --- a/functions.spec.lua +++ b/functions.spec.lua @@ -27,17 +27,34 @@ mtt.register("otp.hmac", function(callback) callback() end) -mtt.register("otp.generate_code", function(callback) +mtt.register("otp.generate_totp", function(callback) local expected_code = 699847 local secret_b32 = "N6JGKMEKU2E6HQMLLNMJKBRRGVQ2ZKV7" local secret = otp.basexx.from_base32(secret_b32) local unix_time = 1640995200 - local tx = 30 - local ct = math.floor(unix_time / tx) - local counter = otp.write_uint64_be(ct) + local code, valid_seconds = otp.generate_totp(secret, unix_time) + assert(code == ""..expected_code) + assert(valid_seconds > 0) - local code = otp.generate_code(secret, counter) - assert(code == expected_code) + code, valid_seconds = otp.generate_totp(secret) + print("Current code: " .. code .. " valid for " .. valid_seconds .. " seconds") + callback() +end) + +mtt.register("otp.create_qr_png", function(callback) + local url = "otpauth://totp/abc:myaccount?algorithm=SHA1&digits=6&issuer=abc&period=30&" + .. "secret=N6JGKMEKU2E6HQMLLNMJKBRRGVQ2ZKV7" + + local ok, code = otp.qrcode(url) + assert(ok) + assert(code) + + local png = otp.create_qr_png(code) + assert(png) + + local f = io.open(minetest.get_worldpath() .. "/qr.png", "w") + f:write(png) + f:close() callback() end) \ No newline at end of file diff --git a/init.lua b/init.lua index 17533e2..4655975 100644 --- a/init.lua +++ b/init.lua @@ -15,5 +15,4 @@ dofile(MP.."/functions.lua") if minetest.get_modpath("mtt") and mtt.enabled then dofile(MP.."/functions.spec.lua") - dofile(MP.."/qrencode.spec.lua") end \ No newline at end of file diff --git a/qrencode.spec.lua b/qrencode.spec.lua deleted file mode 100644 index fb48891..0000000 --- a/qrencode.spec.lua +++ /dev/null @@ -1,13 +0,0 @@ - -mtt.register("otp.qrcode", function(callback) - assert(type(otp.qrcode) == "function") - - local url = "otpauth://totp/abc:myaccount?algorithm=SHA1&digits=6&issuer=abc&period=30&" - .. "secret=N6JGKMEKU2E6HQMLLNMJKBRRGVQ2ZKV7" - - local ok, code = otp.qrcode(url) - assert(ok) - assert(code) - - callback() -end) \ No newline at end of file