diff --git a/sys/apps/Network.lua b/sys/apps/Network.lua index f9f4cc1..1c8405d 100644 --- a/sys/apps/Network.lua +++ b/sys/apps/Network.lua @@ -137,10 +137,14 @@ end ]] function page.ports.grid:update() + local transport = network:getTransport() + local function findConnection(port) - for _,socket in pairs(_G.transport.sockets) do - if socket.sport == port then - return socket + if transport then + for _,socket in pairs(transport.sockets) do + if socket.sport == port then + return socket + end end end end diff --git a/sys/apps/netdaemon.lua b/sys/apps/netdaemon.lua index fb9e23e..2457a49 100644 --- a/sys/apps/netdaemon.lua +++ b/sys/apps/netdaemon.lua @@ -14,6 +14,7 @@ if not device.wireless_modem then end print('Net daemon starting') +device.wireless_modem.closeAll() for _,file in pairs(fs.list('sys/apps/network')) do local fn, msg = Util.run(_ENV, 'sys/apps/network/' .. file) diff --git a/sys/apps/network/keygen.lua b/sys/apps/network/keygen.lua new file mode 100644 index 0000000..65f04b8 --- /dev/null +++ b/sys/apps/network/keygen.lua @@ -0,0 +1,39 @@ +local ECC = require('opus.crypto.ecc') +local Event = require('opus.event') +local Util = require('opus.util') + +local network = _G.network +local os = _G.os + +local keyPairs = { } + +local function generateKeyPair() + local key = { } + for _ = 1, 32 do + table.insert(key, ("%02x"):format(math.random(0, 0xFF))) + end + local privateKey = Util.hexToByteArray(table.concat(key)) + return privateKey, ECC.publicKey(privateKey) +end + +getmetatable(network).__index.getKeyPair = function() + local keys = table.remove(keyPairs) + os.queueEvent('generate_keypair') + if not keys then + return generateKeyPair() + end + return table.unpack(keys) +end + +-- Generate key pairs in the background as this is a time-consuming process +Event.on('generate_keypair', function() + while true do + os.sleep(5) + local timer = Util.timer() + table.insert(keyPairs, { generateKeyPair() }) + _G._syslog('Generated keypair in ' .. timer()) + if #keyPairs >= 3 then + break + end + end +end) diff --git a/sys/apps/network/transport.lua b/sys/apps/network/transport.lua index fa1978c..316f46a 100644 --- a/sys/apps/network/transport.lua +++ b/sys/apps/network/transport.lua @@ -6,7 +6,9 @@ ]]-- local Event = require('opus.event') +local SHA = require('opus.crypto.sha2') +local network = _G.network local os = _G.os local computerId = os.getComputerID() @@ -15,7 +17,10 @@ local transport = { sockets = { }, UID = 0, } -_G.transport = transport + +getmetatable(network).__index.getTransport = function() + return transport +end function transport.open(socket) transport.UID = transport.UID + 1 @@ -33,19 +38,11 @@ function transport.read(socket) end function transport.write(socket, data) - --_syslog('>> ' .. Util.tostring({ type = 'DATA', seq = socket.wseq })) socket.transmit(socket.dport, socket.dhost, data) - - --local timerId = os.startTimer(3) - - --transport.timers[timerId] = socket - --socket.timers[socket.wseq] = timerId - - socket.wseq = socket.wseq + 1 + socket.wseq = SHA.digest(socket.wseq):toHex() end function transport.ping(socket) - --_syslog('>> ' .. Util.tostring({ type = 'DATA', seq = socket.wseq })) if os.clock() - socket.activityTimer > 10 then socket.activityTimer = os.clock() socket.transmit(socket.dport, socket.dhost, { @@ -53,7 +50,7 @@ function transport.ping(socket) seq = -1, }) - local timerId = os.startTimer(5) + local timerId = os.startTimer(3) transport.timers[timerId] = socket socket.timers[-1] = timerId end @@ -78,18 +75,19 @@ Event.on('modem_message', function(_, _, dport, dhost, msg, distance) local socket = transport.sockets[dport] if socket and socket.connected then - --if msg.type then _syslog('<< ' .. Util.tostring(msg)) end if socket.co and coroutine.status(socket.co) == 'dead' then _G._syslog('socket coroutine dead') socket:close() elseif msg.type == 'DISC' then -- received disconnect from other end - if socket.connected then - os.queueEvent('transport_' .. socket.uid) + if msg.seq == socket.rseq then + if socket.connected then + os.queueEvent('transport_' .. socket.uid) + end + socket.connected = false + socket:close() end - socket.connected = false - socket:close() elseif msg.type == 'ACK' then local ackTimerId = socket.timers[msg.seq] @@ -108,28 +106,19 @@ Event.on('modem_message', function(_, _, dport, dhost, msg, distance) }) elseif msg.type == 'DATA' and msg.data then - socket.activityTimer = os.clock() if msg.seq ~= socket.rseq then print('transport seq error - closing socket ' .. socket.sport) _syslog(msg.data) - _syslog('current ' .. socket.rseq) - _syslog('expected ' .. msg.seq) --- socket:close() --- os.queueEvent('transport_' .. socket.uid) + _syslog('expected ' .. socket.rseq) + _syslog('got ' .. msg.seq) else - socket.rseq = socket.rseq + 1 + socket.activityTimer = os.clock() + socket.rseq = SHA.digest(socket.rseq):toHex() table.insert(socket.messages, { msg.data, distance }) - -- use resume instead ?? if not socket.messages[2] then -- table size is 1 os.queueEvent('transport_' .. socket.uid) end - - --_syslog('>> ' .. Util.tostring({ type = 'ACK', seq = msg.seq })) - --socket.transmit(socket.dport, socket.dhost, { - -- type = 'ACK', - -- seq = msg.seq, - --}) end end end diff --git a/sys/apps/network/trust.lua b/sys/apps/network/trust.lua index b31acbc..58d3847 100644 --- a/sys/apps/network/trust.lua +++ b/sys/apps/network/trust.lua @@ -4,6 +4,8 @@ local Security = require('opus.security') local Socket = require('opus.socket') local Util = require('opus.util') +local trustId = '01c3ba27fe01383a03a1785276d99df27c3edcef68fbf231ca' + local function trustConnection(socket) local data = socket:read(2) if data then @@ -14,7 +16,7 @@ local function trustConnection(socket) data = Crypto.decrypt(data, password) if data and data.pk and data.dh == socket.dhost then local trustList = Util.readTable('usr/.known_hosts') or { } - trustList[data.dh] = Util.byteArrayToHex(data.pk) + trustList[data.dh] = data.pk Util.writeTable('usr/.known_hosts', trustList) socket:write({ success = true, msg = 'Trust accepted' }) @@ -29,7 +31,7 @@ Event.addRoutine(function() print('trust: listening on port 19') while true do - local socket = Socket.server(19) + local socket = Socket.server(19, { identifier = trustId }) print('trust: connection from ' .. socket.dhost) diff --git a/sys/apps/trust.lua b/sys/apps/trust.lua index b1283f8..9d036c9 100644 --- a/sys/apps/trust.lua +++ b/sys/apps/trust.lua @@ -27,15 +27,16 @@ if not password then end print('connecting...') -local socket, msg = Socket.connect(remoteId, 19) +local trustId = '01c3ba27fe01383a03a1785276d99df27c3edcef68fbf231ca' +local socket, msg = Socket.connect(remoteId, 19, { identifier = trustId }) if not socket then error(msg) end -local publicKey = Security.getPublicKey() +local identifier = Security.getIdentifier() -socket:write(Crypto.encrypt({ pk = publicKey, dh = os.getComputerID() }, SHA.compute(password))) +socket:write(Crypto.encrypt({ pk = identifier, dh = os.getComputerID() }, SHA.compute(password))) local data = socket:read(2) socket:close() diff --git a/sys/modules/opus/crypto/chacha20.lua b/sys/modules/opus/crypto/chacha20.lua index b08d09a..3b3569a 100644 --- a/sys/modules/opus/crypto/chacha20.lua +++ b/sys/modules/opus/crypto/chacha20.lua @@ -2,7 +2,7 @@ -- By Anavrins local sha2 = require('opus.crypto.sha2') -local util = require('opus.util') +local Util = require('opus.util') local ROUNDS = 20 -- Adjust this for speed tradeoff @@ -115,7 +115,7 @@ local function crypt(data, key, nonce, cntr, round) cntr = tonumber(cntr) or 1 round = tonumber(round) or 20 - local throttle = util.throttle() + local throttle = Util.throttle() local out = {} local state = initState(key, nonce, cntr) local blockAmt = math.floor(#data/64) @@ -157,8 +157,8 @@ local function encrypt(data, key) end local function decrypt(data, key) - local nonce = util.hexToByteArray(data[1]) - data = util.hexToByteArray(data[2]) + local nonce = Util.hexToByteArray(data[1]) + data = Util.hexToByteArray(data[2]) key = sha2.digest(key) local ptx = crypt(data, key, nonce, 1, ROUNDS) return textutils.unserialise(tostring(ptx)) diff --git a/sys/modules/opus/crypto/ecc/elliptic.lua b/sys/modules/opus/crypto/ecc/elliptic.lua index 4177258..9af2683 100644 --- a/sys/modules/opus/crypto/ecc/elliptic.lua +++ b/sys/modules/opus/crypto/ecc/elliptic.lua @@ -22,6 +22,8 @@ -- Indistinguishability? No: The curve does not support indistinguishability maps. local fp = require('opus.crypto.ecc.fp') +local Util = require('opus.util') + local eq = fp.eq local mul = fp.mul local sqr = fp.sqr @@ -31,6 +33,7 @@ local shr = fp.shr local mont = fp.mont local invMont = fp.invMont local sub192 = fp.sub192 +local unpack = table.unpack local bits = 192 local pMinusTwoBinary = {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} @@ -203,20 +206,23 @@ local function scalarMul(s, P1) end local Q = {{unpack(ZERO)}, {unpack(ONE)}, {unpack(ONE)}} - for i = #naf, 1, -1 do + for i = #naf, 1, -1 do -- can this loop be optimized ? + local n = naf[i] Q = pointDouble(Q) - if naf[i] > 0 then - Q = pointAdd(Q, PTable[naf[i]]) - elseif naf[i] < 0 then - Q = pointSub(Q, PTable[-naf[i]]) + if n > 0 then + Q = pointAdd(Q, PTable[n]) + elseif n < 0 then + Q = pointSub(Q, PTable[-n]) end end return Q end +local throttle = Util.throttle() for i = 2, 196 do GTable[i] = pointDouble(GTable[i - 1]) + throttle() end local function scalarMulG(s) diff --git a/sys/modules/opus/crypto/ecc/fp.lua b/sys/modules/opus/crypto/ecc/fp.lua index 3a8c4c6..9e791bd 100644 --- a/sys/modules/opus/crypto/ecc/fp.lua +++ b/sys/modules/opus/crypto/ecc/fp.lua @@ -1,5 +1,7 @@ -- Fp Integer Arithmetic +local unpack = table.unpack + local n = 0xffff local m = 0x10000 diff --git a/sys/modules/opus/crypto/ecc/fq.lua b/sys/modules/opus/crypto/ecc/fq.lua index 277f64d..c552688 100644 --- a/sys/modules/opus/crypto/ecc/fq.lua +++ b/sys/modules/opus/crypto/ecc/fq.lua @@ -1,5 +1,7 @@ -- Fq Integer Arithmetic +local unpack = table.unpack + local n = 0xffff local m = 0x10000 diff --git a/sys/modules/opus/crypto/ecc/init.lua b/sys/modules/opus/crypto/ecc/init.lua index e1c1284..980f699 100644 --- a/sys/modules/opus/crypto/ecc/init.lua +++ b/sys/modules/opus/crypto/ecc/init.lua @@ -3,6 +3,7 @@ local elliptic = require('opus.crypto.ecc.elliptic') local sha256 = require('opus.crypto.sha2') local os = _G.os +local unpack = table.unpack local q = {1372, 62520, 47765, 8105, 45059, 9616, 65535, 65535, 65535, 65535, 65535, 65532} diff --git a/sys/modules/opus/crypto/sha2.lua b/sys/modules/opus/crypto/sha2.lua index 631488a..2754ee7 100644 --- a/sys/modules/opus/crypto/sha2.lua +++ b/sys/modules/opus/crypto/sha2.lua @@ -1,8 +1,8 @@ -- SHA-256, HMAC and PBKDF2 functions in ComputerCraft -- By Anavrins +local Util = require('opus.util') local bit = _G.bit -local os = _G.os local mod32 = 2^32 local band = bit32 and bit32.band or bit.band local bnot = bit32 and bit32.bnot or bit.bnot @@ -162,25 +162,13 @@ local function hmac(data, key) return digest(padded_key) end -local function throttler() - local ts = os.clock() - local timeout = .095 - return function() - local nts = os.clock() - if nts > ts + timeout then - os.sleep(0) - ts = os.clock() - end - end -end - local function pbkdf2(pass, salt, iter, dklen) salt = type(salt) == "table" and salt or {tostring(salt):byte(1,-1)} local hashlen = 32 dklen = dklen or 32 local block = 1 local out = {} - local throttle = throttler() + local throttle = Util.throttle() while dklen > 0 do local ikey = {} diff --git a/sys/modules/opus/security.lua b/sys/modules/opus/security.lua index a44605c..599f793 100644 --- a/sys/modules/opus/security.lua +++ b/sys/modules/opus/security.lua @@ -1,6 +1,6 @@ local Config = require('opus.config') -local Util = require('opus.util') local ECC = require('opus.crypto.ecc') +local Util = require('opus.util') local Security = { } @@ -21,16 +21,6 @@ local function genKey() return table.concat(key) end -function Security.generateKeyPair() - local privateKey = Util.hexToByteArray(genKey()) - return privateKey, ECC.publicKey(privateKey) -end - -function Security.getIdentifier() - return Security.getPublicKey() -end - --- deprecate - will use getIdentifier function Security.getSecretKey() local config = Config.load('os') if not config.secretKey then @@ -40,9 +30,17 @@ function Security.getSecretKey() return Util.hexToByteArray(config.secretKey) end -function Security.getPublicKey() - local secretKey = Security.getSecretKey() - return ECC.publicKey(secretKey) +function Security.getIdentifier() + local config = Config.load('os') + if config.identifier then + return config.identifier + end + -- preserve the hash the user generated + local identifier = ECC.publicKey(Security.getSecretKey()) + config.identifier = Util.byteArrayToHex(identifier) + Config.update('os', config) + + return config.identifier end function Security.updatePassword(password) diff --git a/sys/modules/opus/socket.lua b/sys/modules/opus/socket.lua index bb3b6ef..721f8e4 100644 --- a/sys/modules/opus/socket.lua +++ b/sys/modules/opus/socket.lua @@ -6,11 +6,12 @@ local Util = require('opus.util') local device = _G.device local os = _G.os +local network = _G.network local socketClass = { } function socketClass:read(timeout) - local data, distance = _G.transport.read(self) + local data, distance = network.getTransport().read(self) if data then return data, distance end @@ -25,7 +26,7 @@ function socketClass:read(timeout) local e, id = os.pullEvent() if e == 'transport_' .. self.uid then - data, distance = _G.transport.read(self) + data, distance = network.getTransport().read(self) if data then os.cancelTimer(timerId) return data, distance @@ -46,7 +47,7 @@ end function socketClass:write(data) if self.connected then - _G.transport.write(self, { + network.getTransport().write(self, { type = 'DATA', seq = self.wseq, data = data, @@ -57,30 +58,31 @@ end function socketClass:ping() if self.connected then - _G.transport.ping(self) + network.getTransport().ping(self) return true end end -function socketClass:setupEncryption() - if false then +function socketClass:setupEncryption(x) +local timer = Util.timer() self.sharedKey = ECC.exchange(self.privKey, self.remotePubKey) self.enckey = SHA.pbkdf2(self.sharedKey, "1enc", 1) self.hmackey = SHA.pbkdf2(self.sharedKey, "2hmac", 1) - self.rseed = SHA.pbkdf2(self.sharedKey, "3rseed", 1) - self.wseed = SHA.pbkdf2(self.sharedKey, "4sseed", 1) - end + self.rseq = SHA.pbkdf2(self.sharedKey, x and "3rseed" or "4sseed", 1):toHex() + self.wseq = SHA.pbkdf2(self.sharedKey, x and "4sseed" or "3rseed", 1):toHex() +_syslog('shared in ' .. timer()) end function socketClass:close() if self.connected then self.transmit(self.dport, self.dhost, { type = 'DISC', + seq = self.wseq, }) self.connected = false end device.wireless_modem.close(self.sport) - _G.transport.close(self) + network.getTransport().close(self) end local Socket = { } @@ -115,27 +117,24 @@ local function newSocket(isLoopback) error('No ports available') end -function Socket.connect(host, port) +function Socket.connect(host, port, options) if not device.wireless_modem then return false, 'Wireless modem not found', 'NOMODEM' end - +local timer = Util.timer() local socket = newSocket(host == os.getComputerID()) socket.dhost = tonumber(host) - socket.privKey, socket.pubKey = Security.generateKeyPair() + socket.privKey, socket.pubKey = network.getKeyPair() + local identifier = options and options.identifier or Security.getIdentifier() socket.transmit(port, socket.sport, { type = 'OPEN', shost = socket.shost, dhost = socket.dhost, - rseq = socket.wseq, - wseq = socket.rseq, - t = Crypto.encrypt({ - ts = os.time(), - seq = socket.seq, - nts = os.epoch('utc'), + t = Crypto.encrypt({ -- this is not that much data... + ts = os.epoch('utc'), pk = Util.byteArrayToHex(socket.pubKey), - }, Security.getPublicKey()), + }, Util.hexToByteArray(identifier)), }) local timerId = os.startTimer(3) @@ -152,10 +151,11 @@ function Socket.connect(host, port) socket.dport = dport socket.connected = true socket.remotePubKey = Util.hexToByteArray(msg.pk) - socket:setupEncryption() + socket:setupEncryption(true) -- Logger.log('socket', 'connection established to %d %d->%d', -- host, socket.sport, socket.dport) - _G.transport.open(socket) + network.getTransport().open(socket) +_syslog('connection in ' .. timer()) return socket elseif msg.type == 'NOPASS' then @@ -173,35 +173,30 @@ function Socket.connect(host, port) return false, 'Connection timed out', 'TIMEOUT' end -local function trusted(socket, msg, port) - if port == 19 or msg.shost == os.getComputerID() then - -- no auth for trust server or loopback - return true +local function trusted(socket, msg, options) + local function getIdentifier() + local trustList = Util.readTable('usr/.known_hosts') or { } + return trustList[msg.shost] end - if not Security.hasPassword() then - -- no password has been set on this computer - --return true - end + local identifier = options and options.identifier or getIdentifier() - local trustList = Util.readTable('usr/.known_hosts') or { } - local pubKey = trustList[msg.shost] + if identifier and msg.t and type(msg.t) == 'table' then + local data = Crypto.decrypt(msg.t, Util.hexToByteArray(identifier)) - if pubKey and msg.t then - local data = Crypto.decrypt(msg.t, Util.hexToByteArray(pubKey)) - - if data and data.nts then -- upgraded security - if data.nts and tonumber(data.nts) and math.abs(os.epoch('utc') - data.nts) < 1024 then + if data and data.ts and tonumber(data.ts) then +_G._syslog('time diff ' .. math.abs(os.epoch('utc') - data.ts)) + if math.abs(os.epoch('utc') - data.ts) < 4096 then socket.remotePubKey = Util.hexToByteArray(data.pk) + socket.privKey, socket.pubKey = network.getKeyPair() + socket:setupEncryption() + return true end end - - --local sharedKey = modexp(pubKey, exchange.secretKey, public.primeMod) - return data and data.ts and tonumber(data.ts) and math.abs(os.time() - data.ts) < 24 end end -function Socket.server(port) +function Socket.server(port, options) device.wireless_modem.open(port) -- Logger.log('socket', 'Waiting for connections on port ' .. port) @@ -219,6 +214,7 @@ function Socket.server(port) socket.dhost = msg.shost socket.wseq = msg.wseq socket.rseq = msg.rseq + socket.options = options if not Security.hasPassword() then socket.transmit(socket.dport, socket.sport, { @@ -228,10 +224,8 @@ function Socket.server(port) }) socket:close() - elseif trusted(socket, msg, port) then + elseif trusted(socket, msg, options) then socket.connected = true - socket.privKey, socket.pubKey = Security.generateKeyPair() - socket:setupEncryption() socket.transmit(socket.dport, socket.sport, { type = 'CONN', dhost = socket.dhost, @@ -241,7 +235,7 @@ function Socket.server(port) -- Logger.log('socket', 'Connection established %d->%d', socket.sport, socket.dport) - _G.transport.open(socket) + network.getTransport().open(socket) return socket else diff --git a/sys/modules/opus/util.lua b/sys/modules/opus/util.lua index 271fcfd..9e2d63d 100644 --- a/sys/modules/opus/util.lua +++ b/sys/modules/opus/util.lua @@ -20,6 +20,7 @@ function Util.hexToByteArray(str) end function Util.byteArrayToHex(tbl) + if not tbl then error('byteArrayToHex: invalid table', 2) end return ("%02x"):rep(#tbl):format(unpack(tbl)) end