206 lines
6.5 KiB
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)
|