#51 send serialized data to properly MAC

This commit is contained in:
Mikayla Fischler 2023-06-27 18:36:16 -04:00
parent fb3c7ded06
commit a8071db08e
5 changed files with 144 additions and 48 deletions

File diff suppressed because one or more lines are too long

View File

@ -77,7 +77,7 @@ function threads.thread__main(smem, init)
loop_clock.start() loop_clock.start()
-- send updated data -- send updated data
if not nic.connected() then if nic.connected() then
if plc_comms.is_linked() then if plc_comms.is_linked() then
smem.q.mq_comms_tx.push_command(MQ__COMM_CMD.SEND_STATUS) smem.q.mq_comms_tx.push_command(MQ__COMM_CMD.SEND_STATUS)
else else

View File

@ -149,7 +149,6 @@ function comms.scada_packet()
dest_addr = comms.BROADCAST, dest_addr = comms.BROADCAST,
seq_num = -1, seq_num = -1,
protocol = PROTOCOL.SCADA_MGMT, protocol = PROTOCOL.SCADA_MGMT,
mac = "",
length = 0, length = 0,
payload = {} payload = {}
} }
@ -170,7 +169,7 @@ function comms.scada_packet()
self.protocol = protocol self.protocol = protocol
self.length = #payload self.length = #payload
self.payload = 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 end
-- parse in a modem message as a SCADA packet -- 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") -- log.debug("comms.scada_packet.receive(): discarding packet with distance " .. distance .. " outside of trusted range")
else else
if type(self.raw) == "table" then if type(self.raw) == "table" then
if #self.raw == 6 then if #self.raw == 5 then
self.src_addr = self.raw[1] self.src_addr = self.raw[1]
self.dest_addr = self.raw[2] self.dest_addr = self.raw[2]
self.seq_num = self.raw[3] self.seq_num = self.raw[3]
self.protocol = self.raw[4] self.protocol = self.raw[4]
self.mac = self.raw[5]
-- element 6 must be a table -- element 5 must be a table
if type(self.raw[6]) == "table" then if type(self.raw[5]) == "table" then
self.length = #self.raw[6] self.length = #self.raw[5]
self.payload = self.raw[6] self.payload = self.raw[5]
end end
else else
self.src_addr = nil self.src_addr = nil
self.dest_addr = nil self.dest_addr = nil
self.seq_num = nil self.seq_num = nil
self.protocol = nil self.protocol = nil
self.mac = ""
self.length = 0 self.length = 0
self.payload = {} self.payload = {}
end end
@ -228,7 +225,6 @@ function comms.scada_packet()
type(self.dest_addr) == "number" and type(self.dest_addr) == "number" and
type(self.seq_num) == "number" and type(self.seq_num) == "number" and
type(self.protocol) == "number" and type(self.protocol) == "number" and
type(self.mac) == "string" and
type(self.payload) == "table" type(self.payload) == "table"
end end
end end
@ -236,20 +232,12 @@ function comms.scada_packet()
return self.valid return self.valid
end 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 -- -- public accessors --
---@nodiscard ---@nodiscard
function public.modem_event() return self.modem_msg_in end function public.modem_event() return self.modem_msg_in end
---@nodiscard ---@nodiscard
function public.raw_sendable() return self.raw end 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 ---@nodiscard
function public.local_channel() return self.modem_msg_in.s_channel end function public.local_channel() return self.modem_msg_in.s_channel end
@ -268,8 +256,6 @@ function comms.scada_packet()
---@nodiscard ---@nodiscard
function public.protocol() return self.protocol end function public.protocol() return self.protocol end
---@nodiscard ---@nodiscard
function public.mac() return self.mac end
---@nodiscard
function public.length() return self.length end function public.length() return self.length end
---@nodiscard ---@nodiscard
function public.data() return self.payload end function public.data() return self.payload end
@ -277,6 +263,112 @@ function comms.scada_packet()
return public return public
end 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<br> -- MODBUS packet<br>
-- modeled after MODBUS TCP packet -- modeled after MODBUS TCP packet
---@nodiscard ---@nodiscard

View File

@ -55,7 +55,7 @@ end
---@nodiscard ---@nodiscard
---@param message string initial value concatenated with ciphertext ---@param message string initial value concatenated with ciphertext
local function compute_hmac(message) local function compute_hmac(message)
local start = util.time_ms() -- local start = util.time_ms()
c_eng.hmac.init() c_eng.hmac.init()
c_eng.hmac.update(stream.fromString(message)) c_eng.hmac.update(stream.fromString(message))
@ -63,7 +63,7 @@ local function compute_hmac(message)
local hash = c_eng.hmac.asHex() 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 return hash
end end
@ -166,20 +166,22 @@ function network.nic(modem)
-- send a packet, with message authentication if configured -- send a packet, with message authentication if configured
---@param dest_channel integer destination channel ---@param dest_channel integer destination channel
---@param local_channel integer local 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) function public.transmit(dest_channel, local_channel, packet)
if self.connected then if self.connected then
local tx_packet = packet ---@type authd_packet|scada_packet
if c_eng.hmac ~= nil then if c_eng.hmac ~= nil then
local start = util.time_ms() -- local start = util.time_ms()
local message = textutils.serialize(packet.raw_verifiable(), { allow_repetitions = true, compact = true }) tx_packet = comms.authd_packet()
local computed_hmac = compute_hmac(message)
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 end
modem.transmit(dest_channel, local_channel, packet.raw_sendable()) modem.transmit(dest_channel, local_channel, tx_packet.raw_sendable())
end end
end end
@ -197,25 +199,30 @@ function network.nic(modem)
if self.connected then if self.connected then
local s_packet = comms.scada_packet() local s_packet = comms.scada_packet()
-- parse packet as generic SCADA packet
s_packet.receive(side, sender, reply_to, message, distance)
if s_packet.is_valid() then
if c_eng.hmac ~= nil then if c_eng.hmac ~= nil then
local start = util.time_ms() -- parse packet as an authenticated SCADA packet
local packet_hmac = s_packet.mac() local a_packet = comms.authd_packet()
local computed_hmac = compute_hmac(textutils.serialize(s_packet.raw_verifiable(), { allow_repetitions = true, compact = true })) a_packet.receive(side, sender, reply_to, message, distance)
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 if packet_hmac == computed_hmac then
log.debug("crypto.modem.receive: HMAC verified in " .. (util.time_ms() - start) .. "ms") -- log.debug("crypto.modem.receive: HMAC verified in " .. (util.time_ms() - start) .. "ms")
packet = s_packet s_packet.receive(side, sender, reply_to, textutils.unserialize(msg), distance)
else 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
end end
else else
packet = s_packet -- parse packet as a generic SCADA packet
end s_packet.receive(side, sender, reply_to, message, distance)
end end
if s_packet.is_valid() then packet = s_packet end
end end
return packet return packet

View File

@ -78,13 +78,10 @@ function supervisor.comms(_version, nic, fp_ok)
---@param distance integer ---@param distance integer
---@return modbus_frame|rplc_frame|mgmt_frame|crdn_frame|nil packet ---@return modbus_frame|rplc_frame|mgmt_frame|crdn_frame|nil packet
function public.parse_packet(side, sender, reply_to, message, distance) 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 pkt = nil
local s_pkt = comms.scada_packet()
-- parse packet as generic SCADA packet if s_pkt then
s_pkt.receive(side, sender, reply_to, message, distance)
if s_pkt.is_valid() then
-- get as MODBUS TCP packet -- get as MODBUS TCP packet
if s_pkt.protocol() == PROTOCOL.MODBUS_TCP then if s_pkt.protocol() == PROTOCOL.MODBUS_TCP then
local m_pkt = comms.modbus_packet() local m_pkt = comms.modbus_packet()