Add all of the RA stuff

This commit is contained in:
Soph :3 2025-11-12 14:52:45 +02:00
parent dc56dab4f9
commit a4fd1dbcbf
9 changed files with 962 additions and 4 deletions

View file

@ -39,14 +39,14 @@ local function traverse_and_download(folder_data, prefix)
for _, dir in ipairs(folder_data.dirs or {}) do
fs.makeDir(download_root .. "/" .. prefix .. dir.href)
local subdir_url = base_url .. "/" .. dir.href .. "?ls"
local response = http.get(subdir_url)
local new_prefix = (prefix ~= "" and (prefix .. "/") or "") .. dir.href
local subdir_url = base_url .. "/" .. new_prefix .. "?ls" local response = http.get(subdir_url)
if response then
local body = response.readAll()
if not body then return end
response.close()
local subdir_data = textutils.unserializeJSON(body)
traverse_and_download(subdir_data, prefix .. dir.href)
traverse_and_download(subdir_data, new_prefix)
else
print("Failed to get subdirectory: " .. subdir_url)
end

View file

@ -0,0 +1,208 @@
local g = string.gsub
sha256 = loadstring(g(g(g(g(g(g(g(g('Sa=XbandSb=XbxWSc=XlshiftSd=unpackSe=2^32SYf(g,h)Si=g/2^hSj=i%1Ui-j+j*eVSYk(l,m)Sn=l/2^mUn-n%1VSo={0x6a09e667Tbb67ae85T3c6ef372Ta54ff53aT510e527fT9b05688cT1f83d9abT5be0cd19}Sp={0x428a2f98T71374491Tb5c0fbcfTe9b5dba5T3956c25bT59f111f1T923f82a4Tab1c5ed5Td807aa98T12835b01T243185beT550c7dc3T72be5d74T80deb1feT9bdc06a7Tc19bf174Te49b69c1Tefbe4786T0fc19dc6T240ca1ccT2de92c6fT4a7484aaT5cb0a9dcT76f988daT983e5152Ta831c66dTb00327c8Tbf597fc7Tc6e00bf3Td5a79147T06ca6351T14292967T27b70a85T2e1b2138T4d2c6dfcT53380d13T650a7354T766a0abbT81c2c92eT92722c85Ta2bfe8a1Ta81a664bTc24b8b70Tc76c51a3Td192e819Td6990624Tf40e3585T106aa070T19a4c116T1e376c08T2748774cT34b0bcb5T391c0cb3T4ed8aa4aT5b9cca4fT682e6ff3T748f82eeT78a5636fT84c87814T8cc70208T90befffaTa4506cebTbef9a3f7Tc67178f2}SYq(r,q)if e-1-r[1]<q then r[2]=r[2]+1;r[1]=q-(e-1-r[1])-1 else r[1]=r[1]+qVUrVSYs(t)Su=#t;t[#t+1]=0x80;while#t%64~=56Zt[#t+1]=0VSv=q({0,0},u*8)fWw=2,1,-1Zt[#t+1]=a(k(a(v[w]TFF000000),24)TFF)t[#t+1]=a(k(a(v[w]TFF0000),16)TFF)t[#t+1]=a(k(a(v[w]TFF00),8)TFF)t[#t+1]=a(v[w]TFF)VUtVSYx(y,w)Uc(y[w]W0,24)+c(y[w+1]W0,16)+c(y[w+2]W0,8)+(y[w+3]W0)VSYz(t,w,A)SB={}fWC=1,16ZB[C]=x(t,w+(C-1)*4)VfWC=17,64ZSD=B[C-15]SE=b(b(f(B[C-15],7),f(B[C-15],18)),k(B[C-15],3))SF=b(b(f(B[C-2],17),f(B[C-2],19)),k(B[C-2],10))B[C]=(B[C-16]+E+B[C-7]+F)%eVSG,h,H,I,J,j,K,L=d(A)fWC=1,64ZSM=b(b(f(J,6),f(J,11)),f(J,25))SN=b(a(J,j),a(Xbnot(J),K))SO=(L+M+N+p[C]+B[C])%eSP=b(b(f(G,2),f(G,13)),f(G,22))SQ=b(b(a(G,h),a(G,H)),a(h,H))SR=(P+Q)%e;L,K,j,J,I,H,h,G=K,j,J,(I+O)%e,H,h,G,(O+R)%eVA[1]=(A[1]+G)%e;A[2]=(A[2]+h)%e;A[3]=(A[3]+H)%e;A[4]=(A[4]+I)%e;A[5]=(A[5]+J)%e;A[6]=(A[6]+j)%e;A[7]=(A[7]+K)%e;A[8]=(A[8]+L)%eUAVUY(t)t=t W""t=type(t)=="string"and{t:byte(1,-1)}Wt;t=s(t)SA={d(o)}fWw=1,#t,64ZA=z(t,w,A)VU("%08x"):rep(8):format(d(A))V',"S"," local "),"T",",0x"),"U"," return "),"V"," end "),"W","or "),"X","bit32."),"Y","function "),"Z"," do "))()
local aead = require("lib.plc.aead_chacha_poly")
local function tobytes(s) return {s:byte(1, #s)} end
local function frombytes(t) return string.char(table.unpack(t)) end
local b64abc = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'
local function b64u_encode(bin)
local t, n, out = tobytes(bin), #bin, {}
local i=1
while i<=n do
local a=t[i] or 0; local b=t[i+1] or 0; local c=t[i+2] or 0
local triple = a*65536 + b*256 + c
out[#out+1]=b64abc:sub(bit32.band(bit32.rshift(triple,18), 63)+1,bit32.band(bit32.rshift(triple,18), 63)+1)
out[#out+1]=b64abc:sub(bit32.band(bit32.rshift(triple,12), 63)+1,bit32.band(bit32.rshift(triple,12), 63)+1)
out[#out+1]= i+1<=n and b64abc:sub(bit32.band(bit32.rshift(triple,6), 63)+1,bit32.band(bit32.rshift(triple,6), 63)+1) or ''
out[#out+1]= i+2<=n and b64abc:sub(bit32.band(bit32.rshift(triple,0), 63)+1,bit32.band(bit32.rshift(triple,0), 63)+1) or ''
i=i+3
end
return table.concat(out)
end
local function b64u_decode(txt)
local map = {}
for i=1,#b64abc do map[b64abc:sub(i,i)] = i-1 end
local out = {}
local i=1
while i<=#txt do
local a = map[txt:sub(i,i)]; i=i+1
local b = map[txt:sub(i,i)]; i=i+1
local c = map[txt:sub(i,i)]; i=i+1
local d = map[txt:sub(i,i)]; i=i+1
if a==nil or b==nil then break end
local triple = bit32.bor(bit32.lshift(a,18) , bit32.lshift(b,12) , bit32.lshift(c or 0,6) , (d or 0))
out[#out+1] = string.char(bit32.band(bit32.rshift(triple,16) , 255))
if c ~= nil then out[#out+1] = string.char(bit32.band(bit32.rshift(triple,8) , 255)) end
if d ~= nil then out[#out+1] = string.char(bit32.band(triple , 255)) end
end
return table.concat(out)
end
local function hmac_sha256(key, msg)
local block = 64
if #key > block then key = (sha256(key):gsub('..', function(h) return string.char(tonumber(h,16)) end)) end
if #key < block then key = key .. string.rep("\0", block - #key) end
local o_key_pad = key:gsub('.', function(c) return string.char(bit32.bxor(string.byte(c), 0x5c)) end)
local i_key_pad = key:gsub('.', function(c) return string.char(bit32.bxor(string.byte(c) , 0x36)) end)
local inner = sha256(i_key_pad .. msg)
inner = inner:gsub('..', function(h) return string.char(tonumber(h,16)) end)
local mac = sha256(o_key_pad .. inner)
return mac:gsub('..', function(h) return string.char(tonumber(h,16)) end)
end
local function int_be(n) -- 32-bit BE
return string.char(bit32.band(bit32.rshift(n,24), 255), bit32.band(bit32.rshift(n,16), 255), bit32.band(bit32.rshift(n,8), 255), bit32.band(n, 255))
end
local function pbkdf2_sha256(password, salt, iterations, dkLen)
local hLen = 32
local l = math.ceil(dkLen / hLen)
local r = dkLen - (l-1)*hLen
local dk = {}
for i=1,l do
local U = hmac_sha256(password, salt .. int_be(i))
local T = {U:byte(1,hLen)}
for itr=2,iterations do
U = hmac_sha256(password, U)
local Ub = {U:byte(1,hLen)}
for j=1,hLen do T[j] = bit32.bxor(T[j], Ub[j]) end
if itr % 500 == 0 then sleep(0) end
end
if i<l then
dk[#dk+1] = string.char(table.unpack(T))
else
dk[#dk+1] = string.char(table.unpack(T, 1, r))
end
end
return table.concat(dk)
end
-- HMAC-DRBG
local SEED_PATH = "siss_drg_seed.bin"
local DRBG = {K = string.rep("\0",32), V = string.rep("\1",32), inited=false}
local function drbg_update(provided_data)
DRBG.K = hmac_sha256(DRBG.K, DRBG.V .. "\0" .. (provided_data or ""))
DRBG.V = hmac_sha256(DRBG.K, DRBG.V)
if provided_data and #provided_data>0 then
DRBG.K = hmac_sha256(DRBG.K, DRBG.V .. "\1" .. provided_data)
DRBG.V = hmac_sha256(DRBG.K, DRBG.V)
end
end
local function drbg_init(seed)
DRBG.K = string.rep("\0",32)
DRBG.V = string.rep("\1",32)
drbg_update(seed)
DRBG.inited = true
end
local function drbg_bytes(n)
if not DRBG.inited then error("DRBG not seeded. Call seed_entropy() once.") end
local out = {}
while #table.concat(out) < n do
DRBG.V = hmac_sha256(DRBG.K, DRBG.V)
out[#out+1] = DRBG.V
end
drbg_update("")
local buf = table.concat(out)
return buf:sub(1,n)
end
local function seed_entropy(extra_bytes)
local accum = {}
if fs.exists(SEED_PATH) then
local f = fs.open(SEED_PATH, "rb"); accum[#accum+1]=f.readAll(); f.close()
local seed = sha256(table.concat(accum)):gsub('..', function(h) return string.char(tonumber(h,16)) end)
drbg_init(seed)
else
term.write("Move around / mash keys then press Enter 4 times to seed…\n")
for i=1,4 do
local t1 = os.clock(); read(); local t2 = os.clock()
accum[#accum+1] = int_be(math.floor((t2 - t1)*1e9)) .. tostring({}):sub(8)
end
if extra_bytes and #extra_bytes>0 then accum[#accum+1]=extra_bytes end
local seed = sha256(table.concat(accum)):gsub('..', function(h) return string.char(tonumber(h,16)) end)
drbg_init(seed)
local f = fs.open(SEED_PATH, "wb")
f.write(drbg_bytes(48))
f.close()
end
end
local VERSION = "\1"
local KDF_ID = "\1"
local AAD = "SiSS RA rev1|chacha20poly1305"
local function u32be(n) return string.char(bit32.band(bit32.rshift(n,24),255), bit32.band(bit32.rshift(n,16),255), bit32.band(bit32.rshift(n,8),255), bit32.band(n,255)) end
local function u16be(n) return string.char(bit32.band(bit32.rshift(n,8),255), bit32.band(n,255)) end
local function pack_params(iter)
return u32be(iter) .. u16be(32)
end
local function unpack_params(s)
local i1,i2,i3,i4, d1,d2 = s:byte(1,6)
local iters = (bit32.bor(bit32.lshift(i1,24),bit32.lshift(i2,16),bit32.lshift(i3,8),i4))
local dklen = (bit32.bor(bit32.lshift(d1,8),d2))
return iters, dklen
end
local function random_bytes(n) return drbg_bytes(n) end
local function split_nonce12(nonce12)
assert(#nonce12 == 12, "nonce must be 12 bytes")
local iv = nonce12:sub(1, 6)
local constant = nonce12:sub(7, 12)
return iv, constant
end
local function encrypt_with_password(password, plaintext)
local salt = random_bytes(16)
local iters = 1000
local key = pbkdf2_sha256(password, salt, iters, 32)
local nonce12 = random_bytes(12)
local iv, constant = split_nonce12(nonce12)
local ciphertext, tag = aead.encrypt(AAD, key, iv, constant, plaintext)
local params = pack_params(iters)
local blob = table.concat{ VERSION, KDF_ID, params, salt, nonce12, ciphertext, tag }
return b64u_encode(blob)
end
local function decrypt_with_password(password, blob)
local bin = b64u_decode(blob)
if #bin < 1+1+6+16+12+16 then return nil, "ciphertext too short" end
local pos = 1
local ver = bin:sub(pos,pos); pos=pos+1
if ver ~= VERSION then return nil, "version mismatch" end
local kdfid = bin:sub(pos,pos); pos=pos+1
if kdfid ~= KDF_ID then return nil, "kdf mismatch" end
local params = bin:sub(pos,pos+5); pos=pos+6
local iters, dklen = unpack_params(params); if dklen ~= 32 then return nil, "bad dkLen" end
local salt = bin:sub(pos,pos+15); pos=pos+16
-- read the same 12-byte nonce and split the same way
local nonce12 = bin:sub(pos,pos+11); pos=pos+12
local iv, constant = split_nonce12(nonce12)
local tag_len = 16
local tag = bin:sub(#bin - tag_len + 1)
local ciphertext = bin:sub(pos, #bin - tag_len)
local key = pbkdf2_sha256(password, salt, iters, 32)
local pt, err = aead.decrypt(AAD, key, iv, constant, ciphertext, tag)
if not pt then return nil, err or "auth failed" end
return pt
end
return {
decrypt_with_password = decrypt_with_password,
encrypt_with_password = encrypt_with_password,
seed_entropy = seed_entropy
}

View file

@ -0,0 +1,97 @@
-- Copyright (c) 2015 Phil Leblanc -- see LICENSE file
------------------------------------------------------------
--[[
aead_chacha_poly
Authenticated Encryption with Associated Data (AEAD) [1], based
on Chacha20 stream encryption and Poly1305 MAC, as defined
in RFC 7539 [2].
[1] https://en.wikipedia.org/wiki/Authenticated_encryption
[2] http://www.rfc-editor.org/rfc/rfc7539.txt
This file uses chacha20.lua and poly1305 for the encryption
and MAC primitives.
]]
local chacha20 = require "lib.plc.chacha20"
local poly1305 = require "lib.plc.poly1305"
------------------------------------------------------------
-- poly1305 key generation
local poly_keygen = function(key, nonce)
local counter = 0
local m = string.rep('\0', 64)
local e = chacha20.encrypt(key, counter, nonce, m)
-- keep only first the 256 bits (32 bytes)
return e:sub(1, 32)
end
local pad16 = function(s)
-- return null bytes to add to s so that #s is a multiple of 16
return (#s % 16 == 0) and "" or ('\0'):rep(16 - (#s % 16))
end
local app = table.insert
local encrypt = function(aad, key, iv, constant, plain)
-- aad: additional authenticated data - arbitrary length
-- key: 32-byte string
-- iv, constant: concatenated to form the nonce (12 bytes)
-- (why not one 12-byte param? --maybe because IPsec uses
-- an 8-byte nonce)
-- implementation: RFC 7539 sect 2.8.1
-- (memory inefficient - encr text is copied in mac_data)
local mt = {} -- mac_data table
local nonce = constant .. iv
local otk = poly_keygen(key, nonce)
local encr = chacha20.encrypt(key, 1, nonce, plain)
app(mt, aad)
app(mt, pad16(aad))
app(mt, encr)
app(mt, pad16(encr))
-- aad and encrypted text length must be encoded as
-- little endian _u64_ (and not u32) -- see errata at
-- https://www.rfc-editor.org/errata_search.php?rfc=7539
app(mt, string.pack('<I8', #aad))
app(mt, string.pack('<I8', #encr))
local mac_data = table.concat(mt)
--~ p16('mac', mac_data)
local tag = poly1305.auth(mac_data, otk)
return encr, tag
end --chacha20_aead_encrypt()
local function decrypt(aad, key, iv, constant, encr, tag)
-- (memory inefficient - encr text is copied in mac_data)
-- (structure similar to aead_encrypt => what could be factored?)
local mt = {} -- mac_data table
local nonce = constant .. iv
local otk = poly_keygen(key, nonce)
app(mt, aad)
app(mt, pad16(aad))
app(mt, encr)
app(mt, pad16(encr))
app(mt, string.pack('<I8', #aad))
app(mt, string.pack('<I8', #encr))
local mac_data = table.concat(mt)
local mac = poly1305.auth(mac_data, otk)
if mac == tag then
local plain = chacha20.encrypt(key, 1, nonce, encr)
return plain
else
return nil, "auth failed"
end
end --chacha20_aead_decrypt()
------------------------------------------------------------
-- return aead_chacha_poly module
return {
poly_keygen = poly_keygen,
encrypt = encrypt,
decrypt = decrypt,
}

204
src/lib/plc/chacha20.lua Normal file
View file

@ -0,0 +1,204 @@
-- Copyright (c) 2018 Phil Leblanc -- see LICENSE file
------------------------------------------------------------
--[[
Chacha20 stream encryption
Pure Lua implementation of the chacha20 algorithm
This implements the IETF variant of chacha20 encryption
as defined in RFC 7539 (12-byte nonce) and the xchacha20
variant (same encryption algorithm, but with a 24-byte nonce)
For the combined authenticated encryption with associated
data (AEAD) based on chacha20 encryption and poly1305
authentication, see the aead_chacha20.lua file
See also:
- many chacha20 links at
http://ianix.com/pub/chacha-deployment.html
]]
local app, concat = table.insert, table.concat
------------------------------------------------------------
local function qround(st, x, y, z, w)
-- st is a chacha state: an array of 16 u32 words
-- x,y,z,w are indices in st
local a, b, c, d = st[x], st[y], st[z], st[w]
local t
a = (a + b) % 0x100000000
t = bit32.bxor(d, a)
d = bit32.band(bit32.bor(bit32.lshift(t, 16), bit32.rshift(t, 16)), 0xffffffff)
c = (c + d) % 0x100000000
t = bit32.bxor(b, c)
b = bit32.band(bit32.bor(bit32.lshift(t, 12), bit32.rshift(t, 20)), 0xffffffff)
a = (a + b) % 0x100000000
t = bit32.bxor(d, a)
d = bit32.band(bit32.bor(bit32.lshift(t, 8), bit32.rshift(t, 24)), 0xffffffff)
c = (c + d) % 0x100000000
t = bit32.bxor(b, c)
b = bit32.band(bit32.bor(bit32.lshift(t, 7), bit32.rshift(t, 25)), 0xffffffff)
st[x], st[y], st[z], st[w] = a, b, c, d
return st
end
-- chacha20 state and working state are allocated once and reused
-- by each invocation of chacha20_block()
local chacha20_state = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
local chacha20_working_state = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
local chacha20_block = function(key, counter, nonce)
-- key: u32[8]
-- counter: u32
-- nonce: u32[3]
local st = chacha20_state -- state
local wst = chacha20_working_state -- working state
-- initialize state
st[1], st[2], st[3], st[4] =
0x61707865, 0x3320646e, 0x79622d32, 0x6b206574
for i = 1, 8 do st[i+4] = key[i] end
st[13] = counter
for i = 1, 3 do st[i+13] = nonce[i] end
-- copy state to working_state
for i = 1, 16 do wst[i] = st[i] end
-- run 20 rounds, ie. 10 iterations of 8 quarter rounds
for _ = 1, 10 do --RFC reference:
qround(wst, 1,5,9,13) --1. QUARTERROUND ( 0, 4, 8,12)
qround(wst, 2,6,10,14) --2. QUARTERROUND ( 1, 5, 9,13)
qround(wst, 3,7,11,15) --3. QUARTERROUND ( 2, 6,10,14)
qround(wst, 4,8,12,16) --4. QUARTERROUND ( 3, 7,11,15)
qround(wst, 1,6,11,16) --5. QUARTERROUND ( 0, 5,10,15)
qround(wst, 2,7,12,13) --6. QUARTERROUND ( 1, 6,11,12)
qround(wst, 3,8,9,14) --7. QUARTERROUND ( 2, 7, 8,13)
qround(wst, 4,5,10,15) --8. QUARTERROUND ( 3, 4, 9,14)
end
-- add working_state to state
for i = 1, 16 do st[i] = bit32.band((st[i] + wst[i]), 0xffffffff) end
-- return st, an array of 16 u32 words used as a keystream
return st
end --chacha20_block()
-- pat16: used to unpack a 64-byte string as 16 uint32
local pat16 = "<I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4"
local function chacha20_encrypt_block(key, counter, nonce, pt, ptidx)
-- encrypt a 64-byte block of plain text.
-- key: 32 bytes as an array of 8 uint32
-- counter: an uint32 (must be incremented for each block)
-- nonce: 12 bytes as an array of 3 uint32
-- pt: plain text string,
-- ptidx: index of beginning of block in plain text (origin=1)
-- if less than 64 bytes are left at position ptidx, it is padded
-- with null bytes before encryption and result is stripped
-- accordingly.
-- return encrypted block as a string (length <= 16)
local rbn = #pt - ptidx + 1 -- number of remaining bytes in pt
if rbn < 64 then
local tmp = string.sub(pt, ptidx)
pt = tmp .. string.rep('\0', 64 - rbn) --pad last block
ptidx = 1
end
assert(#pt >= 64)
local ba = table.pack(string.unpack(pat16, pt, ptidx))
local keystream = chacha20_block(key, counter, nonce)
for i = 1, 16 do
ba[i] = bit32.bxor(ba[i], keystream[i])
end
local es = string.pack(pat16, table.unpack(ba))
if rbn < 64 then
es = string.sub(es, 1, rbn)
end
return es
end --chacha20_encrypt_block
local chacha20_encrypt = function(key, counter, nonce, pt)
-- encrypt plain text 'pt', return encrypted text
-- key: 32 bytes as a string
-- counter: an uint32 (must be incremented for each block)
-- nonce: 8 bytes as a string
-- pt: plain text string,
-- ensure counter can fit an uint32 --although it's unlikely
-- that we hit this wall with pure Lua encryption :-)
assert((counter + math.floor(#pt / 64) + 1) < 0xffffffff,
"block counter must fit an uint32")
assert(#key == 32, "#key must be 32")
assert(#nonce == 12, "#nonce must be 12")
local keya = table.pack(string.unpack("<I4I4I4I4I4I4I4I4", key))
local noncea = table.pack(string.unpack("<I4I4I4", nonce))
local t = {} -- used to collect all encrypted blocks
local ptidx = 1
while ptidx <= #pt do
app(t, chacha20_encrypt_block(keya, counter, noncea, pt, ptidx))
ptidx = ptidx + 64
counter = counter + 1
end
local et = concat(t)
return et
end --chacha20_encrypt()
local function hchacha20(key, nonce16)
-- key: string(32)
-- nonce16: string(16)
local keya = table.pack(string.unpack("<I4I4I4I4I4I4I4I4", key))
local noncea = table.pack(string.unpack("<I4I4I4I4", nonce16))
local st = {} -- chacha working state
-- initialize state
st[1], st[2], st[3], st[4] =
0x61707865, 0x3320646e, 0x79622d32, 0x6b206574
for i = 1, 8 do st[i+4] = keya[i] end
for i = 1, 4 do st[i+12] = noncea[i] end
-- run 20 rounds, ie. 10 iterations of 8 quarter rounds
for _ = 1, 10 do --RFC reference:
qround(st, 1,5,9,13) --1. QUARTERROUND ( 0, 4, 8,12)
qround(st, 2,6,10,14) --2. QUARTERROUND ( 1, 5, 9,13)
qround(st, 3,7,11,15) --3. QUARTERROUND ( 2, 6,10,14)
qround(st, 4,8,12,16) --4. QUARTERROUND ( 3, 7,11,15)
qround(st, 1,6,11,16) --5. QUARTERROUND ( 0, 5,10,15)
qround(st, 2,7,12,13) --6. QUARTERROUND ( 1, 6,11,12)
qround(st, 3,8,9,14) --7. QUARTERROUND ( 2, 7, 8,13)
qround(st, 4,5,10,15) --8. QUARTERROUND ( 3, 4, 9,14)
end
local subkey = string.pack("<I4I4I4I4I4I4I4I4",
st[1], st[2], st[3], st[4],
st[13], st[14], st[15], st[16] )
return subkey
end --hchacha20()
local function xchacha20_encrypt(key, counter, nonce, pt)
assert(#key == 32, "#key must be 32")
assert(#nonce == 24, "#nonce must be 24")
local subkey = hchacha20(key, nonce:sub(1, 16))
local nonce12 = '\0\0\0\0'..nonce:sub(17)
return chacha20_encrypt(subkey, counter, nonce12, pt)
end --xchacha20_encrypt()
------------------------------------------------------------
return {
chacha20_encrypt = chacha20_encrypt,
chacha20_decrypt = chacha20_encrypt, -- xor encryption is symmetric
encrypt = chacha20_encrypt, --alias
decrypt = chacha20_encrypt, --alias
hchacha20 = hchacha20,
xchacha20_encrypt = xchacha20_encrypt,
xchacha20_decrypt = xchacha20_encrypt,
--
key_size = 32,
nonce_size = 12, -- nonce size for chacha20_encrypt
xnonce_size = 24, -- nonce size for xchacha20_encrypt
}
--end of chacha20

204
src/lib/plc/poly1305.lua Normal file
View file

@ -0,0 +1,204 @@
-- Copyright (c) 2015 Phil Leblanc -- see LICENSE file
------------------------------------------------------------
--[[
Poly1305 message authentication (MAC) created by Dan Bernstein
...
]]
-----------------------------------------------------------
-- poly1305
local sunp = string.unpack
local bit32 = bit32 -- alias
local function poly_init(k)
-- k: 32-byte key as a string
-- initialize internal state
local st = {
r = {
bit32.band(sunp('<I4', k, 1) , 0x3ffffff), -- r0
bit32.band(bit32.rshift(sunp('<I4', k, 4), 2), 0x3ffff03), -- r1
bit32.band(bit32.rshift(sunp('<I4', k, 7), 4), 0x3ffc0ff), -- r2
bit32.band(bit32.rshift(sunp('<I4', k, 10), 6), 0x3f03fff), -- r3
bit32.band(bit32.rshift(sunp('<I4', k, 13), 8), 0x00fffff), -- r4
},
h = { 0,0,0,0,0 },
pad = { sunp('<I4', k, 17), -- 's' in rfc
sunp('<I4', k, 21),
sunp('<I4', k, 25),
sunp('<I4', k, 29),
},
buffer = "", --
leftover = 0,
final = false,
}--st
return st
end --poly_init()
local function poly_blocks(st, m)
-- st: internal state
-- m: message:string
local bytes = #m
local midx = 1
local hibit = st.final and 0 or 0x01000000 -- 1 << 24
local r0 = st.r[1]
local r1 = st.r[2]
local r2 = st.r[3]
local r3 = st.r[4]
local r4 = st.r[5]
local s1 = r1 * 5
local s2 = r2 * 5
local s3 = r3 * 5
local s4 = r4 * 5
local h0 = st.h[1]
local h1 = st.h[2]
local h2 = st.h[3]
local h3 = st.h[4]
local h4 = st.h[5]
local d0, d1, d2, d3, d4, c
--
while bytes >= 16 do -- 16 = poly1305_block_size
-- h += m[i] (in rfc: a += n with 0x01 byte)
h0 = h0 + bit32.band(sunp('<I4', m, midx ), 0x3ffffff)
h1 = h1 + bit32.band(bit32.rshift(sunp('<I4', m, midx + 3), 2), 0x3ffffff)
h2 = h2 + bit32.band(bit32.rshift(sunp('<I4', m, midx + 6), 4), 0x3ffffff)
h3 = h3 + bit32.band(bit32.rshift(sunp('<I4', m, midx + 9), 6), 0x3ffffff)
h4 = h4 + bit32.bor(bit32.rshift(sunp('<I4', m, midx + 12), 8), hibit) -- 0x01 byte
--
-- h *= r % p (partial)
d0 = h0*r0 + h1*s4 + h2*s3 + h3*s2 + h4*s1
d1 = h0*r1 + h1*r0 + h2*s4 + h3*s3 + h4*s2
d2 = h0*r2 + h1*r1 + h2*r0 + h3*s4 + h4*s3
d3 = h0*r3 + h1*r2 + h2*r1 + h3*r0 + h4*s4
d4 = h0*r4 + h1*r3 + h2*r2 + h3*r1 + h4*r0
--
c = bit32.band(bit32.rshift(d0,26), 0xffffffff) ; h0 = bit32.band(d0, 0x3ffffff)
d1 = d1 + c ; c = bit32.band(bit32.rshift(d1,26), 0xffffffff) ; h1 = bit32.band(d1, 0x3ffffff)
d2 = d2 + c ; c = bit32.band(bit32.rshift(d2,26), 0xffffffff) ; h2 = bit32.band(d2, 0x3ffffff)
d3 = d3 + c ; c = bit32.band(bit32.rshift(d3,26), 0xffffffff) ; h3 = bit32.band(d3, 0x3ffffff)
d4 = d4 + c ; c = bit32.band(bit32.rshift(d4,26), 0xffffffff) ; h4 = bit32.band(d4, 0x3ffffff)
h0 = h0 + (c*5) ; c = bit32.rshift(h0,26) ; h0 = bit32.band(h0, 0x3ffffff)
h1 = h1 + c
--
midx = midx + 16 -- 16 = poly1305_block_size
bytes = bytes - 16
end --while
st.h[1] = h0
st.h[2] = h1
st.h[3] = h2
st.h[4] = h3
st.h[5] = h4
st.bytes = bytes -- remaining bytes. must be < 16 here
st.midx = midx -- index of first remaining bytes
return st
end --poly_blocks()
local function poly_update(st, m)
-- st: internal state
-- m: message:string
st.bytes, st.midx = #m, 1
-- process full blocks if any
if st.bytes >= 16 then
poly_blocks(st, m)
end
--handle remaining bytes
if st.bytes == 0 then -- no bytes left
-- nothing to do? no add 0x01? - apparently not.
else
local buffer = string.sub(m, st.midx)
.. '\x01' .. string.rep('\0', 16 - st.bytes -1)
assert(#buffer == 16)
st.final = true -- this is the last block
--~ p16(buffer)
poly_blocks(st, buffer)
end
--
return st
end --poly_update
local function poly_finish(st)
--
local c, mask --u32
local f --u64
-- fully carry h
local h0 = st.h[1]
local h1 = st.h[2]
local h2 = st.h[3]
local h3 = st.h[4]
local h4 = st.h[5]
--
c = bit32.rshift(h1,26); h1 = bit32.band(h1, 0x3ffffff)
h2 = h2 + c; c = bit32.rshift(h2, 26); h2 = bit32.band(h2, 0x3ffffff)
h3 = h3 + c; c = bit32.rshift(h3, 26); h3 = bit32.band(h3, 0x3ffffff)
h4 = h4 + c; c = bit32.rshift(h4, 26); h4 = bit32.band(h4, 0x3ffffff)
h0 = h0 + (c*5); c = bit32.rshift(h0, 26); h0 = bit32.band(h0, 0x3ffffff)
h1 = h1 + c
--
--compute h + -p
local g0 = (h0 + 5) ; c = bit32.rshift(g0, 26); g0 = bit32.band(g0, 0x3ffffff)
local g1 = (h1 + c) ; c = bit32.rshift(g1, 26); g1 = bit32.band(g1, 0x3ffffff)
local g2 = (h2 + c) ; c = bit32.rshift(g2, 26); g2 = bit32.band(g2, 0x3ffffff)
local g3 = (h3 + c) ; c = bit32.rshift(g3, 26); g3 = bit32.band(g3, 0x3ffffff)
local g4 = bit32.band(h4 + c - 0x4000000, 0xffffffff) -- (1 << 26)
--
-- select h if h < p, or h + -p if h >= p
mask = bit32.band(bit32.rshift(g4, 31) - 1, 0xffffffff)
--
g0 = bit32.band(g0, mask)
g1 = bit32.band(g1, mask)
g2 = bit32.band(g2, mask)
g3 = bit32.band(g3, mask)
g4 = bit32.band(g4, mask)
--
mask = bit32.band(bit32.bnot(mask), 0xffffffff)
h0 = bit32.bor(bit32.band(h0, mask), g0)
h1 = bit32.bor(bit32.band(h1, mask), g1)
h2 = bit32.bor(bit32.band(h2, mask), g2)
h3 = bit32.bor(bit32.band(h3, mask), g3)
h4 = bit32.bor(bit32.band(h4, mask), g4)
--
--h = h % (2^128)
h0 = bit32.band(bit32.bor((h0), bit32.lshift(h1, 26)), 0xffffffff)
h1 = bit32.band(bit32.bor(bit32.rshift(h1, 6), bit32.lshift(h2, 20)), 0xffffffff)
h2 = bit32.band(bit32.bor(bit32.rshift(h2, 12), bit32.lshift(h3, 14)), 0xffffffff)
h3 = bit32.band(bit32.bor(bit32.rshift(h3, 18), bit32.lshift(h4, 8)), 0xffffffff)
--
-- mac = (h + pad) % (2^128)
f = h0 + st.pad[1] ; h0 = bit32.band(f, 0xffffffff)
f = h1 + st.pad[2] + bit32.rshift(f, 32) ; h1 = bit32.band(f, 0xffffffff)
f = h2 + st.pad[3] + bit32.rshift(f, 32) ; h2 = bit32.band(f, 0xffffffff)
f = h3 + st.pad[4] + bit32.rshift(f, 32) ; h3 = bit32.band(f, 0xffffffff)
--
local mac = string.pack('<I4I4I4I4', h0, h1, h2, h3)
-- (should zero out the state?)
--
return mac
end --poly_finish()
local function poly_auth(m, k)
-- m: msg string
-- k: key string (must be 32 bytes)
-- return mac 16-byte string
assert(#k == 32)
local st = poly_init(k)
poly_update(st, m)
local mac = poly_finish(st)
return mac
end --poly_auth()
local function poly_verify(m, k, mac)
local macm = poly_auth(m, k)
return macm == mac
end --poly_verify()
------------------------------------------------------------
-- return poly1305 module
return {
init = poly_init,
update = poly_update,
finish = poly_finish,
auth = poly_auth,
verify = poly_verify,
}

View file

@ -1,11 +1,24 @@
---@class InnerRemote
---@field port number
---@field password string
---@class Remote
---@field ender_storage string
---@field modem string
---@field remotes table<string, InnerRemote>
---
---@class Config
---@field inventories string[]
---@field import string[]|nil
---@field chatbox table<string, string>|nil
---@field remote Remote
local inv = require("modules.inv")
local ui = require("modules.ui")
local chatbox = require("modules.chatbox")
local ra = require("modules.ra")
local config = require("../config") ---@type Config
@ -39,4 +52,4 @@ local function importMechanism()
end
inv.sync()
parallel.waitForAll(chatbox.run, inv.getAIL().run, importMechanism, ui.runUi, inv.detectPlayerInsert)
parallel.waitForAll(inv.getAIL().run, chatbox.run, ra.run, ui.run, importMechanism, inv.detectPlayerInsert)

83
src/modules/ra.lua Normal file
View file

@ -0,0 +1,83 @@
local inv = require("modules.inv");
local config = require("../../config") ---@type Config
local hmac = require("lib.hmac-pbkdf2-aead")
---@class RemoteAccess
---@field type string
---@field data RemoteAccessData
---@class RemoteAccessData
---@field itemName string|nil
---@field amount number|nil
---@field slots number[]|nil
local function run()
if config.remote == nil then return end
local modem = peripheral.wrap(config.remote.modem)
if not modem then return end
for _, remote in pairs(config.remote.remotes) do
if not modem.isOpen(remote.port) then
modem.open(remote.port)
end
end
---@type ccTweaked.peripheral.Inventory
local ender_storage = peripheral.wrap(config.remote.ender_storage)
while true do
local _, _, channel, _, message, _ = os.pullEvent("modem_message")
for color, remote in pairs(config.remote.remotes) do
if remote.port == channel then
local err, data = pcall(function ()
return hmac.decrypt_with_password(remote.password, message)
end)
if not data then return end
if err == true then
---@type RemoteAccess
local json, errj = textutils.unserialiseJSON(data)
if json or errj ~= nil then
local parts = {}
for part in string.gmatch(color, "[^/]+") do
table.insert(parts, part)
end
if #parts ~= 3 then error("Invalid frequency string (need 3 colors)") end
ender_storage.setFrequency(
colors[parts[1]],
colors[parts[2]],
colors[parts[3]]
)
if json.type == "withdraw" then
inv.sendItemToSelf(json.data.itemName, config.remote.ender_storage, json.data.amount)
elseif json.type == "deposit" then
if json.data.itemName then
local move = {}
for s, i in pairs(ender_storage.list()) do
if i.name == json.data.itemName then
table.insert(move, s)
end
end
inv.sendItemAwayMultiple(move, ender_storage, config.remote.ender_storage, json.data.amount)
elseif json.data.slots then
if type(json.data.slots) ~= 'table' then
return
end
inv.sendItemAwayMultiple(json.data.slots, ender_storage, config.remote.ender_storage, json.data.amount)
end
end
end
end
end
end
end
end
return {
run = run
}