diff --git a/example/encrypted_chat/README.md b/example/encrypted_chat/README.md new file mode 100644 index 0000000..6912d6c --- /dev/null +++ b/example/encrypted_chat/README.md @@ -0,0 +1,3 @@ +# E2EE P2P RedNet chat. +This ones a hot mess, apologies for that. +Uses ed25519 keys to get a shared key that isn't transmitted, used to encrypt messages with chacha20 \ No newline at end of file diff --git a/example/encrypted_chat/encchat.lua b/example/encrypted_chat/encchat.lua new file mode 100644 index 0000000..caba740 --- /dev/null +++ b/example/encrypted_chat/encchat.lua @@ -0,0 +1,205 @@ +term.clear() +term.setCursorPos(1, 1) +-- Find modems +for _, sModem in ipairs(peripheral.getNames()) do + if peripheral.getType(sModem) == "modem" then rednet.open(sModem) end +end + +local knownHosts = settings.get("knownhosts") +if knownHosts == nil then + settings.set("knownhosts", {}) + settings.save() +end + +local isClient = true +local PK = nil +local crypto = require("ccryptolib") +crypto.random.initWithTiming() +local to = arg[1] -- Who we're trying to talk to (computer id) +write("Your Nickname: ") +local from = read() +if to == nil then print("You need to provide a computer ID!") end +local hexPrivate = settings.get("keys.private") +local hexPublic = settings.get("keys.public") + +if hexPrivate == nil then + settings.set("keys.private", crypto.util.toHex(crypto.random.random(32))) + hexPrivate = settings.get("keys.private") + settings.save() +end + +if hexPublic == nil then + settings.set("keys.public", crypto.util.toHex(crypto.x25519.publicKey(crypto.util.fromHex(settings.get("keys.private"))))) + hexPublic = settings.get("keys.public") + settings.save() +end + +local privateKey = crypto.util.fromHex(hexPrivate) +local publicKey = crypto.util.fromHex(hexPublic) + +local termX, termY = term.getSize() + +function string.starts(String, Start) + return string.sub(String, 1, string.len(Start)) == Start +end + +function rx() + while true do + sleep(0) + sender, rawData = rednet.receive("encChat") + if sender ~= tonumber(to) then return end + data = textutils.unserialise(crypto.chacha20.crypt(PK, rawData.nonce, rawData.data)) + if not data.test then return end -- Failed decrypt, possible bad key exchange? + term.setCursorPos(1, termY) + term.clearLine() + print(data.from .. ":" .. data.msg) + term.blit("> ", "30", "ff") + term.setCursorPos(3, termY) + end +end + +function tx() + while true do + sleep(0) + term.setCursorPos(1, termY) + term.blit("> ", "30", "ff") + local msg = read() + if (string.starts(msg, "/")) then + -- Command handler + msg = string.lower(msg) + if msg == ("/q" or "/quit" or "/exit") then + error("Goodbye!") + end + else + term.scroll(-1) + term.clearLine() + write(from .. ":" .. msg) + term.scroll(1) + nonce = crypto.random.random(12) + msgData = { + timestamp = os.epoch("utc"), + msg = msg, + from = from, + test = true + } + rednet.send(tonumber(to), { + nonce = nonce, + data = crypto.chacha20 + .crypt(PK, nonce, textutils.serialise(msgData)) + }, "encChat") + end + end +end + +function ping() + local fails = 0 + if isClient then + while true do + sleep(1) + nonce = crypto.random.random(12) + msgData = {timestamp = os.epoch("utc"), test = true} + rednet.send(tonumber(to), { + nonce = nonce, + data = crypto.chacha20 + .crypt(PK, nonce, textutils.serialise(msgData)) + }, "encPing") + + -- Wait for reply + sender, reply = rednet.receive("encPingReply", 5) + if fails > 2 then + error( + "No response from other user, They likely closed their client exiting...") + end + if sender == nil or data == nil then + fails = fails + 1 + elseif crypto.chacha20.crypt(PK, reply.nonce, reply.data) ~= + tostring(msgData.timestamp) then + error("Invalid reply from ping, exiting for security.") + else + fails = 0 + end + end + else + while true do + sleep(1) + sender, data = rednet.receive("encPing", 10) + if fails > 1 then + error("No response from other user, They likely closed their client exiting...") + end + if sender == nil or data == nil then + fails = fails + 1 + elseif sender == tonumber(to) then + timestamp = textutils.unserialise(crypto.chacha20.crypt(PK, data.nonce, data.data)).timestamp + if os.epoch("utc") > timestamp then + fails = 0 + local nonce = crypto.random.random(12) + rednet.send(sender, { + nonce = nonce, + data = crypto.chacha20 + .crypt(PK, nonce, tostring(timestamp)) + }, "encPingReply") + end + end + end + end +end + +function exchange() + print("sending ping to " .. to) + rednet.send(tonumber(to), publicKey, "keyExClient") + sender, data = rednet.receive("keyEx", 1) + if sender ~= tonumber(to) then -- Switch to server + print("no response from " .. to .. ". listening for ping...") + isClient = false + while true do + sender, data = rednet.receive("keyExClient") + if sender == tonumber(to) then + print("ping from " .. sender .. ". sending and generating keys...") + rednet.send(tonumber(to), publicKey, "keyEx") + if knownHosts[tonumber(to)] == nil then + write("remote pubkey is " .. crypto.util.toHex(data) ..". Do you trust it? (Y/N)") + local trust = string.lower(read()) + if trust == "y" then + knownHosts[tonumber(to)] = data + settings.set("knownhosts", knownHosts) + settings.save() + PK = crypto.x25519.exchange(privateKey, data) + else + error("Exiting...") + end + elseif knownHosts[tonumber(to)] ~= data then + error("==DANGER== Remote pubkey differs from previous interaction!") + else + PK = crypto.x25519.exchange(privateKey, data) + end + break + end + end + else + print("response from " .. to) + if knownHosts[tonumber(to)] == nil then + write("remote pubkey is " .. crypto.util.toHex(data) ..". Do you trust it? (Y/N)") + local trust = string.lower(read()) + if trust == "y" then + knownHosts[tonumber(to)] = data + settings.set("knownhosts", knownHosts) + settings.save() + PK = crypto.x25519.exchange(privateKey, data) + else + error("Exiting...") + end + elseif knownHosts[tonumber(to)] ~= data then + error("==DANGER== Remote pubkey differs from previous interaction!") + else + PK = crypto.x25519.exchange(privateKey, data) + end + PK = crypto.x25519.exchange(privateKey, data) + end + term.clear() + term.setCursorPos(1,1) + print("==BEGIN ENCRYPTED CONVERSATION==") +end + +exchange() + +parallel.waitForAll(rx, tx, ping) diff --git a/example/secure_login/README.md b/example/secure_login/README.md new file mode 100644 index 0000000..c84b2c3 --- /dev/null +++ b/example/secure_login/README.md @@ -0,0 +1,4 @@ +# SHA-512 hashed login system +This is a really simple setup to secure a computer in computercraft from them pesky fuckers in your server :P + +Multi user support, but no separation of files or any file permissions at all for that matter. i.e if someone can log in, they can disable the login and change passwords, beware. \ No newline at end of file diff --git a/example/secure_login/passwd.lua b/example/secure_login/passwd.lua new file mode 100644 index 0000000..02f0ca3 --- /dev/null +++ b/example/secure_login/passwd.lua @@ -0,0 +1,28 @@ +local username = arg[1] or _G.loggedInUser +local passwdData = settings.get("passwd") + +if passwdData[username] == nil then + error("passwd: user '" .. username .. "' does not exist") +end + +local crypto = require("ccryptolib") +crypto.random.initWithTiming() + +print("Changing password for " .. username .. ".") +write("New password: ") +local pwd1 = crypto.sha512.digest(read("")) +write("Retype new password: ") +local pwd2 = crypto.sha512.digest(read("")) + +if pwd1 ~= pwd2 then + error("Sorry, passwords do not match.") +end + +if pwd1 == passwdData[username] then + error("The password has not been changed.") +end + +passwdData[username] = crypto.util.toHex(pwd1) +settings.set("passwd", passwdData) +settings.save() +print("passwd: password updated successfully") \ No newline at end of file diff --git a/example/secure_login/startup.lua b/example/secure_login/startup.lua new file mode 100644 index 0000000..6986a06 --- /dev/null +++ b/example/secure_login/startup.lua @@ -0,0 +1,46 @@ +-- No term for you :) +Original_pullEvent = os.pullEvent +os.pullEvent = os.pullEventRaw +settings.set("shell.allow_disk_startup", false) -- Prevent disk startup bypass +-- Header stuff +local hostname = os.getComputerLabel() or "craftos" +term.clear() +term.setCursorPos(1,1) +write(_G._ENV._HOST .. " " .. hostname) + +-- Load crypto library (slow, so we do it after everything else) +Crypto = require("ccryptolib") +Crypto.random.initWithTiming() + +function checkCreds(username, hashed) + + if Crypto.util.fromHex(settings.get("passwd")[username]) == hashed then + return true + else + return false + end +end + +function login() + print() + write(hostname .. " login: ") + local username = read() + write("Password: ") + local passwordHash = Crypto.sha512.digest(read("")) -- Hash immidiately so password is not stored plaintext in memory + if checkCreds(username, passwordHash) then + print("Welcome ".. username) + _G.loggedInUser = username + else + print("Login incorrect") + login() + end +end + +if not settings.get("passwd") then + print() + print("WARNING: passwd file does not exist, generating default login root:toor") + settings.set("passwd", { ["root"] = Crypto.util.toHex(Crypto.sha512.digest("toor")) }) + settings.save() +end + +login() \ No newline at end of file diff --git a/example/secure_login/useradd.lua b/example/secure_login/useradd.lua new file mode 100644 index 0000000..f86a90f --- /dev/null +++ b/example/secure_login/useradd.lua @@ -0,0 +1,28 @@ +local username = arg[1] +local passwdData = settings.get("passwd") + +if passwdData[username] then + error("useradd: user '" .. username .."' already exists") +end + +local crypto = require("ccryptolib") +crypto.random.initWithTiming() + +print("Changing password for " .. username .. ".") +write("New password: ") +local pwd1 = crypto.sha512.digest(read("")) +write("Retype new password: ") +local pwd2 = crypto.sha512.digest(read("")) + +if pwd1 ~= pwd2 then + error("Sorry, passwords do not match.") +end + +if pwd1 == passwdData[username] then + error("The password has not been changed.") +end + +passwdData[username] = crypto.util.toHex(pwd1) +settings.set("passwd", passwdData) +settings.save() +print("useradd: password updated successfully") \ No newline at end of file