--:Minify:-- local kernel = ... local auth = {} kernel.auth = auth -- PASSWD FILE FORMAT: uid:gid:username:homedir:shell -- SHADOW FILE FORMAT: uid:salt:hash local function getFile(path) local file = kernel.vfs.open(path, "r") if not file then error("Failed to open file: " .. path) end local content = kernel.vfs.read(file, 1024000) kernel.vfs.close(file) return content end local function writeFile(path, content) local file = kernel.vfs.open(path, "w") if not file then error("Failed to open file for writing: " .. path) end kernel.vfs.write(file, content) kernel.vfs.close(file) end local blake2s do local MOD32 = 2^32 local function norm(x) return x % MOD32 end local function tobits(x) x = norm(x) local t = {} for i = 0, 31 do local b = x % 2; t[i] = b; x = (x - b) / 2 end return t end local function frombits(t) local x, p = 0, 1 for i = 0, 31 do if t[i] == 1 then x = x + p end; p = p * 2 end return norm(x) end local function bor(...) local args = {...} if #args == 0 then return 0 end local bits = tobits(args[1]) for i = 2, #args do local b = tobits(args[i]) for j = 0, 31 do bits[j] = (bits[j] == 1 or b[j] == 1) and 1 or 0 end end return frombits(bits) end local function bxor(...) local args = {...} if #args == 0 then return 0 end local bits = tobits(args[1]) for i = 2, #args do local b = tobits(args[i]) for j = 0, 31 do bits[j] = (bits[j] ~= b[j]) and 1 or 0 end end return frombits(bits) end local function lshift(x, n) return norm(norm(x) * 2^n) end local function rshift(x, n) return math.floor(norm(x) / 2^n) end local function rotr(x, n) return bor(rshift(x, n), lshift(x, 32 - n)) end local IV = { 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19 } local SIGMA = { {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}, {14,10,4,8,9,15,13,6,1,12,0,2,11,7,5,3}, {11,8,12,0,5,2,15,13,10,14,3,6,7,1,9,4}, {7,9,3,1,13,12,11,14,2,6,5,10,4,0,15,8}, {9,0,5,7,2,4,10,15,14,1,11,12,6,8,3,13}, {2,12,6,10,0,11,8,3,4,13,7,5,15,14,1,9}, {12,5,1,15,14,13,4,10,0,7,6,3,9,2,8,11}, {13,11,7,14,12,1,3,9,5,0,15,4,8,6,2,10}, {6,15,14,9,11,3,0,8,12,2,13,7,1,4,10,5}, {10,2,8,4,7,6,1,5,15,11,9,14,3,12,13,0} } local function G(v, a, b, c, d, x, y) v[a] = (v[a] + v[b] + x) % MOD32 v[d] = rotr(bxor(v[d], v[a]), 16) v[c] = (v[c] + v[d]) % MOD32 v[b] = rotr(bxor(v[b], v[c]), 12) v[a] = (v[a] + v[b] + y) % MOD32 v[d] = rotr(bxor(v[d], v[a]), 8) v[c] = (v[c] + v[d]) % MOD32 v[b] = rotr(bxor(v[b], v[c]), 7) end local function compress(h, block, t, last) local v = {} for i = 1, 8 do v[i] = h[i] end for i = 1, 8 do v[i + 8] = IV[i] end v[13] = bxor(v[13], t) if last then v[15] = bxor(v[15], 0xFFFFFFFF) end local m = {} for i = 0, 15 do local p = i * 4 + 1 m[i] = (block:byte(p) or 0) + ((block:byte(p+1) or 0) * 0x100) + ((block:byte(p+2) or 0) * 0x10000) + ((block:byte(p+3) or 0) * 0x1000000) end for r = 1, 10 do local s = SIGMA[r] G(v,1,5,9,13, m[s[1]], m[s[2]]) G(v,2,6,10,14, m[s[3]], m[s[4]]) G(v,3,7,11,15, m[s[5]], m[s[6]]) G(v,4,8,12,16, m[s[7]], m[s[8]]) G(v,1,6,11,16, m[s[9]], m[s[10]]) G(v,2,7,12,13, m[s[11]], m[s[12]]) G(v,3,8,9,14, m[s[13]], m[s[14]]) G(v,4,5,10,15, m[s[15]], m[s[16]]) end for i = 1, 8 do h[i] = bxor(h[i], v[i], v[i+8]) end end function blake2s(msg, key) key = key or "" local h = {} for i = 1, 8 do h[i] = IV[i] end local outlen = 32 h[1] = bxor(h[1], 0x01010000 + lshift(#key, 8) + outlen) local t = 0 if #key > 0 then local block = key .. string.rep("\0", 64 - #key) t = #key compress(h, block, t, false) end for i = 1, #msg, 64 do local block = msg:sub(i, i + 63) if #block < 64 then block = block .. string.rep("\0", 64 - #block) end t = t + math.min(64, #msg - i + 1) compress(h, block, t, i + 64 > #msg) end local out = "" for i = 1, 8 do out = out .. string.format("%08x", h[i]) end return out end end if not blake2s then error("Failed to load blake2s") end if not kernel.vfs.exists("/etc/pam.d/secret") then kernel.log("PAM SECRET REGENERATING PLEASE USE ROOT") local key = "" for i = 1, 256 do key = key .. string.char(math.random(0, 255)) end local handle = kernel.vfs.open("/etc/pam.d/secret", "w") kernel.vfs.write(handle, key) kernel.vfs.close(handle) end local pepper = getFile("/etc/pam.d/secret") local function genSalt() local chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./" local s = "" for i = 1, 16 do s = s .. chars:sub(math.random(1, #chars), math.random(1, #chars)) end return s end local function hashPassword(password, salt) local key = (pepper .. salt):sub(1, 32) return blake2s(password, key) end local passwdFile = getFile("/etc/passwd") local shadowFile = getFile("/etc/shadow") local passwdLines = string.split(passwdFile, "\n") local shadowLines = string.split(shadowFile, "\n") local passwd, shadow = {}, {} for _, v in ipairs(passwdLines) do local fields = string.split(v, ":") if fields[1] and fields[1] ~= "" then passwd[#passwd + 1] = fields end end for _, v in ipairs(shadowLines) do local fields = string.split(v, ":") if fields[1] and fields[1] ~= "" then shadow[#shadow + 1] = fields end end for _, v in ipairs(passwd) do local uid = tonumber(v[1]) if uid then kernel.users[uid] = v[3] end end kernel.passwd = passwd local function flushPasswd() local lines = {} for _, v in ipairs(passwd) do lines[#lines + 1] = table.concat(v, ":") end writeFile("/etc/passwd", table.concat(lines, "\n")) end local function flushShadow() local lines = {} for _, v in ipairs(shadow) do lines[#lines + 1] = table.concat(v, ":") end writeFile("/etc/shadow", table.concat(lines, "\n")) end local function getPasswdByUID(uid) for _, v in ipairs(passwd) do if tonumber(v[1]) == uid then return v end end return nil end local function getShadowByUID(uid) for _, v in ipairs(shadow) do if tonumber(v[1]) == uid then return v end end return nil end local function getPasswdByUsername(username) for _, v in ipairs(passwd) do if v[3] == username then return v end end return nil end local function nextUID() local max = 999 for _, v in ipairs(passwd) do local uid = tonumber(v[1]) if uid and uid >= 1000 and uid > max then max = uid end end return max + 1 end function auth.login(username, password) if type(username) ~= "string" or type(password) ~= "string" then return nil, "Authentication failure" end local entry = getPasswdByUsername(username) if not entry then -- timing attack resistance hashPassword(password, "aaaaaaaaaaaaaaaa") return nil, "Authentication failure" end local uid = tonumber(entry[1]) local sEntry = getShadowByUID(uid) if not sEntry then hashPassword(password, "aaaaaaaaaaaaaaaa") return nil, "Authentication failure" end local salt = sEntry[2] local storedHash = sEntry[3] local computed = hashPassword(password, salt) if computed ~= storedHash then return nil, "Authentication failure" end kernel.currentUID = uid local _task = kernel.currentTask if _task then _task.uid = uid _task.euid = uid _task.gid = tonumber(entry[2]) or uid _task.egid = tonumber(entry[2]) or uid end kernel.log("AUTH: login uid=" .. tostring(uid) .. " (" .. username .. ")") return true end function auth.setPassword(uid, newPassword) uid = tonumber(uid) if not uid then return nil, "Invalid uid" end local callerUID = (kernel.currentProcess and kernel.currentProcess.euid) or kernel.currentUID or 0 if callerUID ~= 0 and callerUID ~= uid then return nil, "Permission denied" end if type(newPassword) ~= "string" or #newPassword == 0 then return nil, "Password may not be empty" end if #newPassword < 6 then return nil, "Password is too short (minimum 6 characters)" end local salt = genSalt() local hash = hashPassword(newPassword, salt) local sEntry = getShadowByUID(uid) if sEntry then sEntry[2] = salt sEntry[3] = hash else shadow[#shadow + 1] = { tostring(uid), salt, hash } end flushShadow() kernel.log("AUTH: password changed for uid=" .. tostring(uid)) return true end function auth.setUsername(uid, newUsername) uid = tonumber(uid) if not uid then return nil, "Invalid uid" end local callerUID = (kernel.currentProcess and kernel.currentProcess.euid) or kernel.currentUID or 0 if callerUID ~= 0 then return nil, "Permission denied (root only)" end if type(newUsername) ~= "string" or #newUsername == 0 then return nil, "Invalid username" end if not newUsername:match("^[a-z_][a-z0-9_%-]*$") or #newUsername > 32 then return nil, "Invalid username format" end if getPasswdByUsername(newUsername) then return nil, "Username already taken" end local entry = getPasswdByUID(uid) if not entry then return nil, "No such user" end local oldName = entry[3] entry[3] = newUsername kernel.users[uid] = newUsername flushPasswd() kernel.log("AUTH: uid=" .. tostring(uid) .. " renamed '" .. oldName .. "' → '" .. newUsername .. "'") return true end function auth.newUser(username, password, gid, homedir, shell) local callerUID = (kernel.currentProcess and kernel.currentProcess.euid) or kernel.currentUID or 0 if callerUID ~= 0 then return nil, "Permission denied (root only)" end if type(username) ~= "string" or #username == 0 then return nil, "Invalid username" end if not username:match("^[a-z_][a-z0-9_%-]*$") or #username > 32 then return nil, "Invalid username format" end if getPasswdByUsername(username) then return nil, "Username already exists" end if type(password) ~= "string" or #password < 6 then return nil, "Password is too short (minimum 6 characters)" end local uid = nextUID() gid = tonumber(gid) or uid homedir = homedir or ("/home/" .. username) shell = shell or "/bin/hysh" passwd[#passwd + 1] = { tostring(uid), tostring(gid), username, homedir, shell } kernel.users[uid] = username local salt = genSalt() local hash = hashPassword(password, salt) shadow[#shadow + 1] = { tostring(uid), salt, hash } flushPasswd() flushShadow() if kernel.vfs.mkdir and not kernel.vfs.exists(homedir) then kernel.vfs.mkdir(homedir) -- Homedir must be owned by the new user, not root pcall(kernel.vfs.chown, homedir, uid, uid) end kernel.log("AUTH: new user '" .. username .. "' uid=" .. tostring(uid)) return uid end function auth.whoami() local uid = (kernel.currentProcess and kernel.currentProcess.euid) or kernel.currentUID if not uid then return nil, "Not logged in" end return kernel.users[uid] or ("uid=" .. tostring(uid)) end function auth.getUID(username) local entry = getPasswdByUsername(username) if entry then return tonumber(entry[1]) end return nil end function auth.getPasswd(uid) uid = tonumber(uid) local entry = getPasswdByUID(uid) if not entry then return nil end return { uid = tonumber(entry[1]), gid = tonumber(entry[2]), username = entry[3], homedir = entry[4], shell = entry[5], } end function auth.deleteUser(uid) uid = tonumber(uid) if not uid then return nil, "Invalid uid" end local callerUID = kernel.uid or 0 if callerUID ~= 0 then return nil, "Permission denied (root only)" end if uid == 0 then return nil, "Cannot delete root" end local entry = getPasswdByUID(uid) if not entry then return nil, "No such user" end local username = entry[3] for i, v in ipairs(passwd) do if tonumber(v[1]) == uid then table.remove(passwd, i); break end end for i, v in ipairs(shadow) do if tonumber(v[1]) == uid then table.remove(shadow, i); break end end kernel.users[uid] = nil flushPasswd() flushShadow() kernel.log("AUTH: deleted user '" .. username .. "' uid=" .. tostring(uid)) return true end function auth.lockUser(uid) uid = tonumber(uid) if not uid then return nil, "Invalid uid" end local callerUID = kernel.uid or 0 if callerUID ~= 0 then return nil, "Permission denied (root only)" end if uid == 0 then return nil, "Cannot lock root" end local sEntry = getShadowByUID(uid) if not sEntry then return nil, "No shadow entry for uid" end if sEntry[3]:sub(1,1) ~= "!" then sEntry[3] = "!" .. sEntry[3] end flushShadow() kernel.log("AUTH: locked uid=" .. tostring(uid)) return true end function auth.unlockUser(uid) uid = tonumber(uid) if not uid then return nil, "Invalid uid" end local callerUID = kernel.uid or 0 if callerUID ~= 0 then return nil, "Permission denied (root only)" end local sEntry = getShadowByUID(uid) if not sEntry then return nil, "No shadow entry for uid" end if sEntry[3]:sub(1,1) == "!" then sEntry[3] = sEntry[3]:sub(2) end flushShadow() kernel.log("AUTH: unlocked uid=" .. tostring(uid)) return true end function auth.listUsers() local result = {} for _, v in ipairs(passwd) do local uid = tonumber(v[1]) local sEntry = getShadowByUID(uid) local locked = sEntry and sEntry[3]:sub(1,1) == "!" result[#result+1] = { uid = uid, gid = tonumber(v[2]), username = v[3], homedir = v[4], shell = v[5], locked = locked or false, } end return result end function auth.setShell(uid, shell) uid = tonumber(uid) if not uid then return nil, "Invalid uid" end local callerUID = kernel.uid or 0 if callerUID ~= 0 and callerUID ~= uid then return nil, "Permission denied" end if type(shell) ~= "string" or #shell == 0 then return nil, "Invalid shell" end local entry = getPasswdByUID(uid) if not entry then return nil, "No such user" end entry[5] = shell flushPasswd() kernel.log("AUTH: uid=" .. tostring(uid) .. " shell -> " .. shell) return true end function auth.setHomedir(uid, homedir) uid = tonumber(uid) if not uid then return nil, "Invalid uid" end local callerUID = kernel.uid or 0 if callerUID ~= 0 then return nil, "Permission denied (root only)" end if type(homedir) ~= "string" or #homedir == 0 then return nil, "Invalid homedir" end local entry = getPasswdByUID(uid) if not entry then return nil, "No such user" end entry[4] = homedir flushPasswd() kernel.log("AUTH: uid=" .. tostring(uid) .. " homedir -> " .. homedir) return true end function auth.setGID(uid, gid) uid = tonumber(uid) gid = tonumber(gid) if not uid or not gid then return nil, "Invalid uid or gid" end local callerUID = kernel.uid or 0 if callerUID ~= 0 then return nil, "Permission denied (root only)" end local entry = getPasswdByUID(uid) if not entry then return nil, "No such user" end entry[2] = tostring(gid) flushPasswd() kernel.log("AUTH: uid=" .. tostring(uid) .. " gid -> " .. tostring(gid)) return true end function auth.elevate(targetUsername, password) if type(targetUsername) ~= "string" or type(password) ~= "string" then return nil, "Authentication failure" end local entry = getPasswdByUsername(targetUsername) if not entry then hashPassword(password, "aaaaaaaaaaaaaaaa") -- timing resistance return nil, "Authentication failure" end local uid = tonumber(entry[1]) local sEntry = getShadowByUID(uid) if not sEntry then hashPassword(password, "aaaaaaaaaaaaaaaa") return nil, "Authentication failure" end local computed = hashPassword(password, sEntry[2]) if computed ~= sEntry[3] then return nil, "Authentication failure" end local task = kernel.currentTask local prevUid = task.uid task.uid = 0 task.euid = 0 task.gid = 0 task.egid = 0 kernel.uid = 0 kernel.log("AUTH: elevate uid=" .. tostring(prevUid) .. " -> 0 (via " .. targetUsername .. ")") return true, uid end if kernel.syscalls then kernel.syscalls["login"] = auth.login kernel.syscalls["setpassword"] = auth.setPassword kernel.syscalls["setusername"] = auth.setUsername kernel.syscalls["newuser"] = auth.newUser kernel.syscalls["whoami"] = auth.whoami kernel.syscalls["getuidbyname"]= auth.getUID kernel.syscalls["getpasswd"] = auth.getPasswd kernel.syscalls["elevate"] = auth.elevate kernel.syscalls["deleteuser"] = auth.deleteUser kernel.syscalls["lockuser"] = auth.lockUser kernel.syscalls["unlockuser"] = auth.unlockUser kernel.syscalls["listusers"] = auth.listUsers kernel.syscalls["setshell"] = auth.setShell kernel.syscalls["sethomedir"] = auth.setHomedir kernel.syscalls["setgid"] = auth.setGID end