working otp code generation

This commit is contained in:
BuckarooBanzay 2023-01-28 17:25:39 +01:00
parent 64eb6077b3
commit 831ba7772e
9 changed files with 149 additions and 42 deletions

20
.github/workflows/test.yml vendored Normal file
View file

@ -0,0 +1,20 @@
name: test
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
matrix:
ENGINE_VERSION: [5.3.0, 5.4.0, 5.5.0, 5.6.1, latest]
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
submodules: recursive
- name: test
run: docker-compose up --exit-code-from test

15
docker-compose.yml Normal file
View file

@ -0,0 +1,15 @@
version: "3.6"
services:
test:
build: ./test
user: root
volumes:
- "./:/root/.minetest/worlds/world/worldmods/otp/"
- "./test/minetest.conf:/minetest.conf"
- "world:/root/.minetest/worlds/world"
ports:
- "30000:30000/udp"
volumes:
world: {}

View file

@ -1,6 +1,6 @@
-- https://stackoverflow.com/a/25594410 -- https://stackoverflow.com/a/25594410
function otp.bitXOR(a,b)--Bitwise xor local function bitxor(a,b)
local p,c=1,0 local p,c=1,0
while a>0 and b>0 do while a>0 and b>0 do
local ra,rb=a%2,b%2 local ra,rb=a%2,b%2
@ -16,6 +16,16 @@ function otp.bitXOR(a,b)--Bitwise xor
return c return c
end end
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
-- https://stackoverflow.com/a/32387452 -- https://stackoverflow.com/a/32387452
local function bitand(a, b) local function bitand(a, b)
local result = 0 local result = 0
@ -36,15 +46,20 @@ local function rshift(x, by)
return math.floor(x / 2 ^ by) return math.floor(x / 2 ^ by)
end end
function otp.write_uint64(v) local function lshift(x, by)
local b1 = bitand(v, 0xFF) return x * 2 ^ by
local b2 = bitand( rshift(v, 8), 0xFF ) end
local b3 = bitand( rshift(v, 16), 0xFF )
local b4 = bitand( rshift(v, 24), 0xFF ) -- big-endian uint64 of a number
local b5 = bitand( rshift(v, 32), 0xFF ) function otp.write_uint64_be(v)
local b6 = bitand( rshift(v, 40), 0xFF ) local b1 = bitand( rshift(v, 56), 0xFF )
local b7 = bitand( rshift(v, 48), 0xFF ) local b2 = bitand( rshift(v, 48), 0xFF )
local b8 = bitand( rshift(v, 56), 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 )
return string.char(b1, b2, b3, b4, b5, b6, b7, b8) return string.char(b1, b2, b3, b4, b5, b6, b7, b8)
end end
@ -61,29 +76,49 @@ end
function otp.hmac(key, message) function otp.hmac(key, message)
local i_key_pad = "" local i_key_pad = ""
for i=1,64 do for i=1,64 do
i_key_pad = i_key_pad .. string.char(otp.bitXOR(string.byte(key, i) or 0x00, string.byte(i_pad, i))) i_key_pad = i_key_pad .. string.char(bitxor(string.byte(key, i) or 0x00, string.byte(i_pad, i)))
end end
assert(#i_key_pad == 64)
local o_key_pad = "" local o_key_pad = ""
for i=1,64 do for i=1,64 do
o_key_pad = o_key_pad .. string.char(otp.bitXOR(string.byte(key, i) or 0x00, string.byte(o_pad, i))) o_key_pad = o_key_pad .. string.char(bitxor(string.byte(key, i) or 0x00, string.byte(o_pad, i)))
end end
assert(#o_key_pad == 64)
-- concat message -- concat message
local first_msg = i_key_pad local first_msg = i_key_pad
for i=1,#message do for i=1,#message do
first_msg = first_msg .. string.byte(message, i) first_msg = first_msg .. string.char(string.byte(message, i))
end end
assert(#first_msg == 64+8)
-- hash first message -- hash first message
local hash_sum_1 = minetest.sha1(first_msg, true) local hash_sum_1 = minetest.sha1(first_msg, true)
assert(#hash_sum_1 == 20)
-- concat first message to secons -- concat first message to secons
local second_msg = o_key_pad local second_msg = o_key_pad
for i=1,#hash_sum_1 do for i=1,#hash_sum_1 do
second_msg = second_msg .. string.byte(hash_sum_1, i) second_msg = second_msg .. string.char(string.byte(hash_sum_1, i))
end end
assert(#second_msg == 64+20)
-- hash final message local hmac = minetest.sha1(second_msg, true)
return minetest.sha1(second_msg, true) assert(#hmac == 20)
return hmac
end
function otp.generate_code(key, message)
local hmac = otp.hmac(key, message)
-- 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))
return value % 10^6
end end

43
functions.spec.lua Normal file
View file

@ -0,0 +1,43 @@
mtt.register("otp.hmac", function(callback)
local secret_b32 = "N6JGKMEKU2E6HQMLLNMJKBRRGVQ2ZKV7"
local secret = otp.basexx.from_base32(secret_b32)
local unix_time = 1640995200
local expected_hmac = otp.basexx.from_base64("m04YheEb7i+ThPMUJEfVXVybZFo=")
assert(#expected_hmac == 20)
local tx = 30
local ct = math.floor(unix_time / tx)
local counter = otp.write_uint64_be(ct)
assert( #secret == 20 )
assert( otp.basexx.to_base64(secret) == "b5JlMIqmiePBi1tYlQYxNWGsqr8=" )
assert( #counter == 8 )
assert( otp.basexx.to_base64(counter) == "AAAAAANCp0A=" )
local hmac = otp.hmac(secret, counter)
assert(#hmac == 20)
for i=1,20 do
assert( string.byte(expected_hmac,i) == string.byte(hmac, i) )
end
callback()
end)
mtt.register("otp.generate_code", 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 = otp.generate_code(secret, counter)
assert(code == expected_code)
callback()
end)

View file

@ -9,4 +9,7 @@ otp = {
} }
dofile(MP.."/functions.lua") dofile(MP.."/functions.lua")
dofile(MP.."/test.lua")
if minetest.get_modpath("mtt") and mtt.enabled then
dofile(MP.."/functions.spec.lua")
end

View file

@ -1 +1,2 @@
name = otp name = otp
optional_depends = mtt

View file

@ -1,24 +0,0 @@
local secret_b32 = "N6JGKMEKU2E6HQMLLNMJKBRRGVQ2ZKV7"
local expected_code = 699847
minetest.register_chatcommand("otp_test", {
description = "",
params = "[]",
func = function(name, param)
local secret = otp.basexx.from_base32(secret_b32)
local unix_time = 1640995200
local tx = 30
local ct = math.floor(unix_time / tx)
local hmac = otp.hmac(secret, otp.write_uint64(ct))
print(dump({
expected_code = expected_code,
unix_time = unix_time,
ct = ct,
hmac = otp.basexx.to_base64(hmac)
}))
end
})

11
test/Dockerfile Normal file
View file

@ -0,0 +1,11 @@
ARG ENGINE_VERSION=5.6.1
FROM registry.gitlab.com/minetest/minetest/server:${ENGINE_VERSION}
USER root
RUN apk add git &&\
mkdir -p /root/.minetest/worlds/world/worldmods/ &&\
cd /root/.minetest/worlds/world/worldmods/ &&\
git clone https://github.com/BuckarooBanzay/mtt
ENTRYPOINT minetestserver --config /minetest.conf

3
test/minetest.conf Normal file
View file

@ -0,0 +1,3 @@
default_game = minetest_game
mg_name = v7
mtt_enable = true