diff --git a/install_manifest.json b/install_manifest.json index 88da63c..0d4a88f 100644 --- a/install_manifest.json +++ b/install_manifest.json @@ -1 +1 @@ -{"versions": {"installer": "v1.5", "bootloader": "0.2", "comms": "2.1.0", "graphics": "1.0.0", "lockbox": "1.0", "reactor-plc": "v1.5.0", "rtu": "v1.4.0", "supervisor": "v0.18.0", "coordinator": "v0.17.1", "pocket": "alpha-v0.5.1"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/tcd.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua", "scada-common/network.lua"], "graphics": ["graphics/element.lua", "graphics/events.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/listbox.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/multipane.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/led.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/ledpair.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/indicators/ledrgb.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/controls/tabbar.lua", "graphics/elements/controls/sidebar.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/digest/md5.lua", "lockbox/mac/hmac.lua"], "reactor-plc": ["reactor-plc/renderer.lua", "reactor-plc/threads.lua", "reactor-plc/databus.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua", "reactor-plc/panel/front_panel.lua", "reactor-plc/panel/style.lua"], "rtu": ["rtu/renderer.lua", "rtu/threads.lua", "rtu/rtu.lua", "rtu/databus.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/panel/front_panel.lua", "rtu/panel/style.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/renderer.lua", "supervisor/databus.lua", "supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/panel/pgi.lua", "supervisor/panel/front_panel.lua", "supervisor/panel/style.lua", "supervisor/panel/components/rtu_entry.lua", "supervisor/panel/components/pdg_entry.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/pocket.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/turbine.lua", "coordinator/session/pocket.lua", "coordinator/session/apisessions.lua"], "pocket": ["pocket/pocket.lua", "pocket/renderer.lua", "pocket/config.lua", "pocket/coreio.lua", "pocket/startup.lua", "pocket/ui/main.lua", "pocket/ui/style.lua", "pocket/ui/components/conn_waiting.lua", "pocket/ui/pages/turbine_page.lua", "pocket/ui/pages/reactor_page.lua", "pocket/ui/pages/home_page.lua", "pocket/ui/pages/unit_page.lua", "pocket/ui/pages/boiler_page.lua"]}, "depends": {"reactor-plc": ["system", "common", "graphics", "lockbox"], "rtu": ["system", "common", "graphics", "lockbox"], "supervisor": ["system", "common", "graphics", "lockbox"], "coordinator": ["system", "common", "graphics", "lockbox"], "pocket": ["system", "common", "graphics", "lockbox"]}, "sizes": {"manifest": 5567, "system": 1991, "common": 93645, "graphics": 144556, "lockbox": 34900, "reactor-plc": 97702, "rtu": 102350, "supervisor": 315619, "coordinator": 198291, "pocket": 37736}} \ No newline at end of file +{"versions": {"installer": "v1.5", "bootloader": "0.2", "comms": "2.1.0", "graphics": "1.0.0", "lockbox": "1.0", "reactor-plc": "v1.5.0", "rtu": "v1.4.0", "supervisor": "v0.18.0", "coordinator": "v0.17.1", "pocket": "alpha-v0.5.1"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/tcd.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua", "scada-common/network.lua"], "graphics": ["graphics/element.lua", "graphics/events.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/listbox.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/multipane.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/led.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/ledpair.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/indicators/ledrgb.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/controls/tabbar.lua", "graphics/elements/controls/sidebar.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/digest/md5.lua", "lockbox/mac/hmac.lua"], "reactor-plc": ["reactor-plc/renderer.lua", "reactor-plc/threads.lua", "reactor-plc/databus.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua", "reactor-plc/panel/front_panel.lua", "reactor-plc/panel/style.lua"], "rtu": ["rtu/renderer.lua", "rtu/threads.lua", "rtu/rtu.lua", "rtu/databus.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/panel/front_panel.lua", "rtu/panel/style.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/renderer.lua", "supervisor/databus.lua", "supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/panel/pgi.lua", "supervisor/panel/front_panel.lua", "supervisor/panel/style.lua", "supervisor/panel/components/rtu_entry.lua", "supervisor/panel/components/pdg_entry.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/pocket.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/turbine.lua", "coordinator/session/pocket.lua", "coordinator/session/apisessions.lua"], "pocket": ["pocket/pocket.lua", "pocket/renderer.lua", "pocket/config.lua", "pocket/coreio.lua", "pocket/startup.lua", "pocket/ui/main.lua", "pocket/ui/style.lua", "pocket/ui/components/conn_waiting.lua", "pocket/ui/pages/turbine_page.lua", "pocket/ui/pages/reactor_page.lua", "pocket/ui/pages/home_page.lua", "pocket/ui/pages/unit_page.lua", "pocket/ui/pages/boiler_page.lua"]}, "depends": {"reactor-plc": ["system", "common", "graphics", "lockbox"], "rtu": ["system", "common", "graphics", "lockbox"], "supervisor": ["system", "common", "graphics", "lockbox"], "coordinator": ["system", "common", "graphics", "lockbox"], "pocket": ["system", "common", "graphics", "lockbox"]}, "sizes": {"manifest": 5567, "system": 1991, "common": 97110, "graphics": 144556, "lockbox": 34900, "reactor-plc": 97698, "rtu": 102350, "supervisor": 315528, "coordinator": 198291, "pocket": 37736}} \ No newline at end of file diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index 17f6661..cf18963 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -77,7 +77,7 @@ function threads.thread__main(smem, init) loop_clock.start() -- send updated data - if not nic.connected() then + if nic.connected() then if plc_comms.is_linked() then smem.q.mq_comms_tx.push_command(MQ__COMM_CMD.SEND_STATUS) else diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 99a3c7c..88e8631 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -149,7 +149,6 @@ function comms.scada_packet() dest_addr = comms.BROADCAST, seq_num = -1, protocol = PROTOCOL.SCADA_MGMT, - mac = "", length = 0, payload = {} } @@ -170,7 +169,7 @@ function comms.scada_packet() self.protocol = protocol self.length = #payload self.payload = payload - self.raw = { self.src_addr, self.dest_addr, self.seq_num, self.protocol, self.mac, self.payload } + self.raw = { self.src_addr, self.dest_addr, self.seq_num, self.protocol, self.payload } end -- parse in a modem message as a SCADA packet @@ -198,24 +197,22 @@ function comms.scada_packet() -- log.debug("comms.scada_packet.receive(): discarding packet with distance " .. distance .. " outside of trusted range") else if type(self.raw) == "table" then - if #self.raw == 6 then + if #self.raw == 5 then self.src_addr = self.raw[1] self.dest_addr = self.raw[2] self.seq_num = self.raw[3] self.protocol = self.raw[4] - self.mac = self.raw[5] - -- element 6 must be a table - if type(self.raw[6]) == "table" then - self.length = #self.raw[6] - self.payload = self.raw[6] + -- element 5 must be a table + if type(self.raw[5]) == "table" then + self.length = #self.raw[5] + self.payload = self.raw[5] end else self.src_addr = nil self.dest_addr = nil self.seq_num = nil self.protocol = nil - self.mac = "" self.length = 0 self.payload = {} end @@ -228,7 +225,6 @@ function comms.scada_packet() type(self.dest_addr) == "number" and type(self.seq_num) == "number" and type(self.protocol) == "number" and - type(self.mac) == "string" and type(self.payload) == "table" end end @@ -236,20 +232,12 @@ function comms.scada_packet() return self.valid end - -- set message authentication code - function public.set_mac(code) - self.mac = code - self.raw = { self.src_addr, self.dest_addr, self.seq_num, self.protocol, self.mac, self.payload } - end - -- public accessors -- ---@nodiscard function public.modem_event() return self.modem_msg_in end ---@nodiscard function public.raw_sendable() return self.raw end - ---@nodiscard - function public.raw_verifiable() return { self.src_addr, self.dest_addr, self.seq_num, self.protocol, "", self.payload } end ---@nodiscard function public.local_channel() return self.modem_msg_in.s_channel end @@ -268,8 +256,6 @@ function comms.scada_packet() ---@nodiscard function public.protocol() return self.protocol end ---@nodiscard - function public.mac() return self.mac end - ---@nodiscard function public.length() return self.length end ---@nodiscard function public.data() return self.payload end @@ -277,6 +263,112 @@ function comms.scada_packet() return public end +-- authenticated SCADA packet object +---@nodiscard +function comms.authd_packet() + local self = { + modem_msg_in = nil, ---@type modem_message|nil + valid = false, + raw = {}, + src_addr = comms.BROADCAST, + dest_addr = comms.BROADCAST, + mac = "", + payload = "" + } + + ---@class authd_packet + local public = {} + + -- make an authenticated SCADA packet + ---@param s_packet scada_packet scada packet to authenticate + ---@param mac function message authentication function + function public.make(s_packet, mac) + self.valid = true + self.src_addr = s_packet.src_addr() + self.dest_addr = s_packet.dest_addr() + self.payload = textutils.serialize(s_packet.raw_sendable(), { allow_repetitions = true, compact = true }) + self.mac = mac(self.payload) + self.raw = { self.src_addr, self.dest_addr, self.mac, self.payload } + end + + -- parse in a modem message as an authenticated SCADA packet + ---@param side string modem side + ---@param sender integer sender channel + ---@param reply_to integer reply channel + ---@param message any message body + ---@param distance integer transmission distance + ---@return boolean valid valid message received + function public.receive(side, sender, reply_to, message, distance) + ---@class modem_message + self.modem_msg_in = { + iface = side, + s_channel = sender, + r_channel = reply_to, + msg = message, + dist = distance + } + + self.valid = false + self.raw = self.modem_msg_in.msg + + if (type(max_distance) == "number") and (distance > max_distance) then + -- outside of maximum allowable transmission distance + -- log.debug("comms.authd_packet.receive(): discarding packet with distance " .. distance .. " outside of trusted range") + else + if type(self.raw) == "table" then + if #self.raw == 4 then + self.src_addr = self.raw[1] + self.dest_addr = self.raw[2] + self.mac = self.raw[3] + self.payload = self.raw[4] + else + self.src_addr = nil + self.dest_addr = nil + self.mac = "" + self.payload = "" + end + + -- check if this packet is destined for this device + local is_destination = (self.dest_addr == comms.BROADCAST) or (self.dest_addr == COMPUTER_ID) + + self.valid = is_destination and + type(self.src_addr) == "number" and + type(self.dest_addr) == "number" and + type(self.mac) == "string" and + type(self.payload) == "string" + end + end + + return self.valid + end + + -- public accessors -- + + ---@nodiscard + function public.modem_event() return self.modem_msg_in end + ---@nodiscard + function public.raw_sendable() return self.raw end + + ---@nodiscard + function public.local_channel() return self.modem_msg_in.s_channel end + ---@nodiscard + function public.remote_channel() return self.modem_msg_in.r_channel end + + ---@nodiscard + function public.is_valid() return self.valid end + + ---@nodiscard + function public.src_addr() return self.src_addr end + ---@nodiscard + function public.dest_addr() return self.dest_addr end + ---@nodiscard + function public.mac() return self.mac end + ---@nodiscard + function public.data() return self.payload end + + return public +end + -- MODBUS packet
-- modeled after MODBUS TCP packet ---@nodiscard diff --git a/scada-common/network.lua b/scada-common/network.lua index 5c18eda..f0a96e2 100644 --- a/scada-common/network.lua +++ b/scada-common/network.lua @@ -55,7 +55,7 @@ end ---@nodiscard ---@param message string initial value concatenated with ciphertext local function compute_hmac(message) - local start = util.time_ms() + -- local start = util.time_ms() c_eng.hmac.init() c_eng.hmac.update(stream.fromString(message)) @@ -63,7 +63,7 @@ local function compute_hmac(message) local hash = c_eng.hmac.asHex() - log.debug("compute_hmac(): hmac-md5 = " .. util.strval(hash) .. " (took " .. (util.time_ms() - start) .. "ms)") + -- log.debug("compute_hmac(): hmac-md5 = " .. util.strval(hash) .. " (took " .. (util.time_ms() - start) .. "ms)") return hash end @@ -166,20 +166,22 @@ function network.nic(modem) -- send a packet, with message authentication if configured ---@param dest_channel integer destination channel ---@param local_channel integer local channel - ---@param packet scada_packet packet raw_sendable + ---@param packet scada_packet packet function public.transmit(dest_channel, local_channel, packet) if self.connected then + local tx_packet = packet ---@type authd_packet|scada_packet + if c_eng.hmac ~= nil then - local start = util.time_ms() - local message = textutils.serialize(packet.raw_verifiable(), { allow_repetitions = true, compact = true }) - local computed_hmac = compute_hmac(message) + -- local start = util.time_ms() + tx_packet = comms.authd_packet() - packet.set_mac(computed_hmac) + ---@cast tx_packet authd_packet + tx_packet.make(packet, compute_hmac) - log.debug("crypto.modem.transmit: data processing took " .. (util.time_ms() - start) .. "ms") + -- log.debug("crypto.modem.transmit: data processing took " .. (util.time_ms() - start) .. "ms") end - modem.transmit(dest_channel, local_channel, packet.raw_sendable()) + modem.transmit(dest_channel, local_channel, tx_packet.raw_sendable()) end end @@ -197,25 +199,30 @@ function network.nic(modem) if self.connected then local s_packet = comms.scada_packet() - -- parse packet as generic SCADA packet - s_packet.receive(side, sender, reply_to, message, distance) + if c_eng.hmac ~= nil then + -- parse packet as an authenticated SCADA packet + local a_packet = comms.authd_packet() + a_packet.receive(side, sender, reply_to, message, distance) - if s_packet.is_valid() then - if c_eng.hmac ~= nil then - local start = util.time_ms() - local packet_hmac = s_packet.mac() - local computed_hmac = compute_hmac(textutils.serialize(s_packet.raw_verifiable(), { allow_repetitions = true, compact = true })) + if a_packet.is_valid() then + -- local start = util.time_ms() + local packet_hmac = a_packet.mac() + local msg = a_packet.data() + local computed_hmac = compute_hmac(msg) if packet_hmac == computed_hmac then - log.debug("crypto.modem.receive: HMAC verified in " .. (util.time_ms() - start) .. "ms") - packet = s_packet + -- log.debug("crypto.modem.receive: HMAC verified in " .. (util.time_ms() - start) .. "ms") + s_packet.receive(side, sender, reply_to, textutils.unserialize(msg), distance) else - log.debug("crypto.modem.receive: HMAC failed verification in " .. (util.time_ms() - start) .. "ms") + -- log.debug("crypto.modem.receive: HMAC failed verification in " .. (util.time_ms() - start) .. "ms") end - else - packet = s_packet end + else + -- parse packet as a generic SCADA packet + s_packet.receive(side, sender, reply_to, message, distance) end + + if s_packet.is_valid() then packet = s_packet end end return packet diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index b9c14ce..fb33b06 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -78,13 +78,10 @@ function supervisor.comms(_version, nic, fp_ok) ---@param distance integer ---@return modbus_frame|rplc_frame|mgmt_frame|crdn_frame|nil packet function public.parse_packet(side, sender, reply_to, message, distance) + local s_pkt = nic.receive(side, sender, reply_to, message, distance) local pkt = nil - local s_pkt = comms.scada_packet() - -- parse packet as generic SCADA packet - s_pkt.receive(side, sender, reply_to, message, distance) - - if s_pkt.is_valid() then + if s_pkt then -- get as MODBUS TCP packet if s_pkt.protocol() == PROTOCOL.MODBUS_TCP then local m_pkt = comms.modbus_packet()