ccryptolib/example/encrypted_chat/encchat.lua

206 lines
6.5 KiB
Lua

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)