diff --git a/gui/preload-chat.js b/gui/preload-chat.js index 1634b204..ed876a4f 100644 --- a/gui/preload-chat.js +++ b/gui/preload-chat.js @@ -80,6 +80,8 @@ PouchDB.plugin(require("pouchdb-find")); //PouchDB.plugin(require('pouchdb-replication')); PouchDB.plugin(require("pouchdb-upsert")); + + var db = new PouchDB(chatDB); var users = new PouchDB(userDB); @@ -90,14 +92,15 @@ createUserIndex(); // REMOTE SYNC ATTEMPTS //var remoteDB = new PouchDB('http://172.20.10.4:5984/chatDB') + /* + // we need express packages for running pouchdb sync "express-pouchdb" var express = require('express'); var app = express(); -app.use('/chatDB', require('express-pouchdb')(PouchDB)); +app.use('/', require('express-pouchdb')(PouchDB)); app.listen(5984); - - +var db = new PouchDB(chatDB); app.use('/chatDB', require('pouchdb-express-router')(PouchDB)); @@ -818,6 +821,7 @@ ipcRenderer.on("action-new-msg-received", (event, arg) => { obj.checksum = "null"; obj.msg = "null"; obj.status = item.status; + obj.hmac_signed = item.hmac_signed; obj.snr = item.snr; obj.type = "ping"; obj.filename = "null"; @@ -897,6 +901,7 @@ ipcRenderer.on("action-new-msg-received", (event, arg) => { obj.filetype = splitted_data[7]; //obj.file = btoa(splitted_data[8]); obj.file = FD.btoa_FD(splitted_data[8]); + obj.hmac_signed = item.hmac_signed; obj.new = 1; } else if (splitted_data[1] == "req" && splitted_data[2] == "0") { obj.uuid = uuidv4().toString(); @@ -910,6 +915,7 @@ ipcRenderer.on("action-new-msg-received", (event, arg) => { obj.filename = "null"; obj.filetype = "null"; obj.file = "null"; + obj.hmac_signed = item.hmac_signed; obj.new = 0; if (config.enable_request_profile == "True") { sendUserData(item.dxcallsign); @@ -926,6 +932,7 @@ ipcRenderer.on("action-new-msg-received", (event, arg) => { obj.filename = "null"; obj.filetype = "null"; obj.file = "null"; + obj.hmac_signed = item.hmac_signed; obj.new = 0; if (config.enable_request_shared_folder == "True") { sendSharedFolderList(item.dxcallsign); @@ -947,6 +954,7 @@ ipcRenderer.on("action-new-msg-received", (event, arg) => { obj.filename = "null"; obj.filetype = "null"; obj.file = "null"; + obj.hmac_signed = item.hmac_signed; obj.new = 0; if (config.enable_request_shared_folder == "True") { sendSharedFolderFile(item.dxcallsign, name); @@ -963,6 +971,7 @@ ipcRenderer.on("action-new-msg-received", (event, arg) => { obj.filename = "null"; obj.filetype = "null"; obj.file = "null"; + obj.hmac_signed = item.hmac_signed; obj.new = 0; console.log(splitted_data); let userData = new Object(); @@ -992,6 +1001,7 @@ ipcRenderer.on("action-new-msg-received", (event, arg) => { obj.filename = "null"; obj.filetype = "null"; obj.file = "null"; + obj.hmac_signed = item.hmac_signed; obj.new = 0; console.log(splitted_data); @@ -1006,7 +1016,7 @@ ipcRenderer.on("action-new-msg-received", (event, arg) => { //getSetUserInformation(selected_callsign); } else if (splitted_data[1] == "res-2") { - console.log("In received respons-2"); + console.log("In received response-2"); let sharedFileInfo = splitted_data[2].split("/", 2); obj.uuid = uuidv4().toString(); @@ -1020,6 +1030,7 @@ ipcRenderer.on("action-new-msg-received", (event, arg) => { obj.filename = sharedFileInfo[0]; obj.filetype = "application/octet-stream"; obj.file = FD.btoa_FD(sharedFileInfo[1]); + obj.hmac_signed = item.hmac_signed; obj.new = 0; } else { console.log("no rule matched for handling received data!"); @@ -1049,16 +1060,13 @@ update_chat = function (obj) { } return doc; }); - obj.attempt = 1; - } - - // define attempts - if (typeof obj.attempt == "undefined") { var attempt = 1; + obj.attempt = attempt; } else { var attempt = obj.attempt; } + // add percent and bytes per minute if not existing //console.log(obj.percent) if (typeof obj.percent == "undefined") { @@ -1387,6 +1395,18 @@ update_chat = function (obj) { showOsPopUp("Message received from " + obj.dxcallsign, obj.msg); } + + // check if message is signed or not for adjusting icon + if(typeof obj.hmac_signed !== "undefined" && obj.hmac_signed !== "False"){ + console.log(hmac_signed) + var hmac_signed = ''; + } else { + + var hmac_signed = ''; + + } + + var new_message = `
@@ -1401,6 +1421,17 @@ update_chat = function (obj) { ${timestamp}

+ + + + + ${hmac_signed} + +
@@ -1925,6 +1956,7 @@ add_obj_to_database = function (obj) { status: obj.status, snr: obj.snr, attempt: obj.attempt, + hmac_signed: obj.hmac_signed, new: obj.new, _attachments: { [obj.filename]: { @@ -2173,6 +2205,7 @@ function createChatIndex() { "status", "percent", "attempt", + "hmac_signed", "bytesperminute", "_attachments", "new", diff --git a/tnc/data_handler.py b/tnc/data_handler.py index 868b9dab..f1afa51a 100644 --- a/tnc/data_handler.py +++ b/tnc/data_handler.py @@ -6,6 +6,8 @@ Created on Sun Dec 27 20:43:40 2020 """ # pylint: disable=invalid-name, line-too-long, c-extension-no-member # pylint: disable=import-outside-toplevel, attribute-defined-outside-init +# pylint: disable=fixme + import os import base64 @@ -15,7 +17,8 @@ import time import uuid import lzma from random import randrange - +import hmac +import hashlib import codec2 import helpers import modem @@ -326,7 +329,8 @@ class DATA: # [3] mycallsign with ssid # [4] dxcallsign with ssid # [5] attempts - self.open_dc_and_transmit(data[1], data[2], data[3], data[4], data[5]) + # [6] hmac salt hash + self.open_dc_and_transmit(data[1], data[2], data[3], data[4], data[5], data[6]) elif data[0] == "FEC_IS_WRITING": @@ -915,9 +919,27 @@ class DATA: data_frame = payload[9:] data_frame_crc_received = helpers.get_crc_32(data_frame) - # Check if data_frame_crc is equal with received crc - if data_frame_crc == data_frame_crc_received: - self.arq_process_received_data_frame(data_frame, snr) + # check if hmac signing enabled + if TNC.enable_hmac: + self.log.info( + "[TNC] [HMAC] Enabled", + ) + # now check if we have valid hmac signature - returns salt or bool + salt_found = helpers.search_hmac_salt(self.dxcallsign, self.mycallsign, data_frame_crc, data_frame, token_iters=100) + + if salt_found: + # hmac digest received + self.arq_process_received_data_frame(data_frame, snr, signed=True) + + else: + + # hmac signature wrong + self.arq_process_received_data_frame(data_frame, snr, signed=False) + elif data_frame_crc == data_frame_crc_received: + self.log.warning( + "[TNC] [HMAC] Disabled, using CRC", + ) + self.arq_process_received_data_frame(data_frame, snr, signed=False) else: self.send_data_to_socket_queue( freedata="tnc-message", @@ -1019,18 +1041,21 @@ class DATA: # Update modes we are listening to self.set_listening_modes(False, True, self.mode_list[self.speed_level]) - def arq_process_received_data_frame(self, data_frame, snr): + def arq_process_received_data_frame(self, data_frame, snr, signed): """ """ # transmittion duration + + signed = "True" if signed else "False" + duration = time.time() - self.rx_start_of_transmission self.calculate_transfer_rate_rx( self.rx_start_of_transmission, len(ARQ.rx_frame_buffer) ) self.log.info("[TNC] ARQ | RX | DATA FRAME SUCCESSFULLY RECEIVED", nacks=self.frame_nack_counter, - bytesperminute=ARQ.bytes_per_minute, total_bytes=ARQ.total_bytes, duration=duration) + bytesperminute=ARQ.bytes_per_minute, total_bytes=ARQ.total_bytes, duration=duration, hmac_signed=signed) # Decompress the data frame data_frame_decompressed = lzma.decompress(data_frame) @@ -1125,7 +1150,8 @@ class DATA: dxcallsign=str(Station.dxcallsign, "UTF-8"), dxgrid=str(Station.dxgrid, "UTF-8"), data=base64_data, - irs=helpers.bool_to_string(self.is_IRS) + irs=helpers.bool_to_string(self.is_IRS), + hmac_signed=signed ) if TNC.enable_stats: @@ -1150,7 +1176,7 @@ class DATA: snr=snr, ) - def arq_transmit(self, data_out: bytes): + def arq_transmit(self, data_out: bytes, hmac_salt: bytes): """ Transmit ARQ frame @@ -1204,9 +1230,19 @@ class DATA: tx_start_of_transmission = time.time() self.calculate_transfer_rate_tx(tx_start_of_transmission, 0, len(data_out)) - # Append a crc at the beginning and end of file indicators - frame_payload_crc = helpers.get_crc_32(data_out) - self.log.debug("[TNC] frame payload CRC:", crc=frame_payload_crc.hex()) + # check if hmac signature is available + if hmac_salt not in ['', False]: + print(data_out) + # create hmac digest + hmac_digest = hmac.new(hmac_salt, data_out, hashlib.sha256).digest() + # truncate to 32bit + frame_payload_crc = hmac_digest[:4] + self.log.debug("[TNC] frame payload HMAC:", crc=frame_payload_crc.hex()) + + else: + # Append a crc at the beginning and end of file indicators + frame_payload_crc = helpers.get_crc_32(data_out) + self.log.debug("[TNC] frame payload CRC:", crc=frame_payload_crc.hex()) # Assemble the data frame data_out = ( @@ -1269,7 +1305,7 @@ class DATA: tempbuffer = [] self.rpt_request_buffer = [] # Append data frames with n_frames_per_burst to tempbuffer - for n_frame in range(0, n_frames_per_burst): + for n_frame in range(n_frames_per_burst): arqheader = bytearray() arqheader[:1] = bytes([FR_TYPE.BURST_01.value + n_frame]) #####arqheader[:1] = bytes([FR_TYPE.BURST_01.value]) @@ -1643,7 +1679,7 @@ class DATA: print(self.rpt_request_buffer) tempbuffer_rptframes = [] - for i in range(0, len(missing_area)): + for i in range(len(missing_area)): print(missing_area[i]) missing_frames_buffer_position = missing_area[i] - 1 tempbuffer_rptframes.append(self.rpt_request_buffer[missing_frames_buffer_position]) @@ -2061,6 +2097,7 @@ class DATA: mycallsign, dxcallsign, attempts: int, + hmac_salt: str ) -> bool: """ Open data channel and transmit data @@ -2109,7 +2146,7 @@ class DATA: threading.Event().wait(0.01) if ARQ.arq_state: - self.arq_transmit(data_out) + self.arq_transmit(data_out, hmac_salt) return True return False diff --git a/tnc/helpers.py b/tnc/helpers.py index 84bd23ec..844bf2f3 100644 --- a/tnc/helpers.py +++ b/tnc/helpers.py @@ -13,6 +13,13 @@ import structlog import numpy as np import threading import mesh +import hashlib +import hmac +import os +import sys +from pathlib import Path + + log = structlog.get_logger("helpers") @@ -489,3 +496,172 @@ def return_key_from_object(default, obj, key): def bool_to_string(state): return "True" if state else "False" + + + + +def get_hmac_salt(dxcallsign: bytes, mycallsign: bytes): + filename = f"freedata_hmac_STATION_{mycallsign.decode('utf-8')}_REMOTE_{dxcallsign.decode('utf-8')}.txt" + if sys.platform in ["linux"]: + + if hasattr(sys, "_MEIPASS"): + filepath = getattr(sys, "_MEIPASS") + '/hmac/' + filename + else: + subfolder = Path('hmac') + filepath = subfolder / filename + + + elif sys.platform in ["darwin"]: + if hasattr(sys, "_MEIPASS"): + filepath = getattr(sys, "_MEIPASS") + '/hmac/' + filename + else: + subfolder = Path('hmac') + filepath = subfolder / filename + + elif sys.platform in ["win32", "win64"]: + if hasattr(sys, "_MEIPASS"): + filepath = getattr(sys, "_MEIPASS") + '/hmac/' + filename + else: + subfolder = Path('hmac') + filepath = subfolder / filename + else: + try: + subfolder = Path('hmac') + filepath = subfolder / filename + except Exception as e: + log.error( + "[TNC] [HMAC] File lookup error", file=filepath, + ) + + # check if file exists else return false + if not check_if_file_exists(filepath): + return False + + log.info("[SCK] [HMAC] File lookup", file=filepath) + + try: + with open(filepath, "r") as file: + line = file.readlines() + hmac_salt = bytes(line[-1], "utf-8").split(b'\n') + hmac_salt = hmac_salt[0] + return hmac_salt if delete_last_line_from_hmac_list(filepath, -1) else False + except Exception as e: + log.warning("[SCK] [HMAC] File lookup failed", file=filepath, e=e) + return False + +def search_hmac_salt(dxcallsign: bytes, mycallsign: bytes, search_token, data_frame, token_iters): + + filename = f"freedata_hmac_STATION_{mycallsign.decode('utf-8')}_REMOTE_{dxcallsign.decode('utf-8')}.txt" + if sys.platform in ["linux"]: + + if hasattr(sys, "_MEIPASS"): + filepath = getattr(sys, "_MEIPASS") + '/hmac/' + filename + else: + subfolder = Path('hmac') + filepath = subfolder / filename + + + elif sys.platform in ["darwin"]: + if hasattr(sys, "_MEIPASS"): + filepath = getattr(sys, "_MEIPASS") + '/hmac/' + filename + else: + subfolder = Path('hmac') + filepath = subfolder / filename + + elif sys.platform in ["win32", "win64"]: + if hasattr(sys, "_MEIPASS"): + filepath = getattr(sys, "_MEIPASS") + '/hmac/' + filename + else: + subfolder = Path('hmac') + filepath = subfolder / filename + else: + try: + subfolder = Path('hmac') + filepath = subfolder / filename + except Exception as e: + log.error( + "[TNC] [HMAC] File lookup error", file=filepath, + ) + + # check if file exists else return false + if not check_if_file_exists(filepath): + log.warning( + "[TNC] [HMAC] Token file not found", file=filepath, + ) + return False + + try: + with open(filepath, "r") as file: + token_list = file.readlines() + + token_iters = min(token_iters, len(token_list)) + for _ in range(1, token_iters + 1): + key = token_list[len(token_list) - _][:-1] + key = bytes(key, "utf-8") + search_digest = hmac.new(key, data_frame, hashlib.sha256).digest()[:4] + # TODO: Remove this debugging information if not needed anymore + # print("-----------------------------------------") + # print(_) + # print(f" key-------------{key}") + # print(f" key-------------{token_list[len(token_list) - _][:-1]}") + # print(f" key-------------{key.hex()}") + # print(f" search token----{search_token.hex()}") + # print(f" search digest---{search_digest.hex()}") + if search_token.hex() == search_digest.hex(): + token_position = len(token_list) - _ + delete_last_line_from_hmac_list(filepath, token_position) + log.info( + "[TNC] [HMAC] Signature found", expected=search_token.hex(), + ) + return True + + + log.warning( + "[TNC] [HMAC] Signature not found", expected=search_token.hex(), filepath=filepath, + ) + return False + + except Exception as e: + log.warning( + "[TNC] [HMAC] Lookup failed", e=e, expected=search_token, + ) + return False + + +def delete_last_line_from_hmac_list(filepath, position): + # check if file exists else return false + if not check_if_file_exists(filepath): + return False + + try: + linearray = [] + with open(filepath, "r") as file: + linearray = file.readlines()[:position] + #print(linearray) + + with open(filepath, "w") as file: + #print(linearray) + for line in linearray: + file.write(line) + + return True + + except Exception: + return False + +def check_if_file_exists(path): + try: + # check if file size is present and filesize > 0 + if os.path.isfile(path): + filesize = os.path.getsize(path) + if filesize > 0: + return True + else: + return False + else: + return False + except Exception as e: + log.warning( + "[TNC] [FILE] Lookup failed", e=e, path=path, + ) + return False \ No newline at end of file diff --git a/tnc/main.py b/tnc/main.py index 6754df25..fa7ac8bb 100755 --- a/tnc/main.py +++ b/tnc/main.py @@ -253,6 +253,14 @@ if __name__ == "__main__": help="Enable mesh protocol", ) + PARSER.add_argument( + "--hmac", + dest="enable_hmac", + action="store_true", + default=True, + help="Enable and set hmac message salt", + ) + ARGS = PARSER.parse_args() # set save to folder state for allowing downloading files to local file system @@ -307,6 +315,8 @@ if __name__ == "__main__": TCIParam.port = ARGS.tci_port ModemParam.tx_delay = ARGS.tx_delay MeshParam.enable_protocol = ARGS.enable_mesh + TNC.enable_hmac = ARGS.enable_hmac + except Exception as e: log.error("[DMN] Error reading config file", exception=e) diff --git a/tnc/mesh.py b/tnc/mesh.py index 525b21a4..3fb331eb 100644 --- a/tnc/mesh.py +++ b/tnc/mesh.py @@ -53,37 +53,33 @@ class MeshRouter(): self.log = structlog.get_logger("RF") - self.transmission_time_list = [30, 60, 60, 60, 60, 60, 60,120,120,120,120,120,120, 180, 180, 180, 180, 180,180,360,360,360,360,360,360, - 60, 60, 60, 60, 60, 60,120,120,120,120,120,120, 180, 180, 180, 180, 180,180,360,360,360,360,360,360, - 60, 60, 60, 60, 60, 60,120,120,120,120,120,120, 180, 180, 180, 180, 180,180,360,360,360,360,360,360, - 60, 60, 60, 60, 60, 60,120,120,120,120,120,120, 180, 180, 180, 180, 180,180,360,360,360,360,360,360, - 60, 60, 60, 60, 60, 60,120,120,120,120,120,120, 180, 180, 180, 180, 180,180,360,360,360,360,360,360, - 60, 60, 60, 60, 60, 60,120,120,120,120,120,120, 180, 180, 180, 180, 180,180,360,360,360,360,360,360, - 60, 60, 60, 60, 60, 60,120,120,120,120,120,120, 180, 180, 180, 180, 180,180,360,360,360,360,360,360, - 60, 60, 60, 60, 60, 60,120,120,120,120,120,120, 180, 180, 180, 180, 180,180,360,360,360,360,360,360, - 60, 60, 60, 60, 60, 60, 120, 120, 120, 120, 120, 120, 180, 180, 180, 180, 180, 180, 360, 360,360, 360, 360, 360, - 60, 60, 60, 60, 60, 60, 120, 120, 120, 120, 120, 120, 180, 180, 180, 180, 180, 180, 360, 360,360, 360, 360, 360, - 60, 60, 60, 60, 60, 60, 120, 120, 120, 120, 120, 120, 180, 180, 180, 180, 180, 180, 360, 360, - 360, 360, 360, 360, - 60, 60, 60, 60, 60, 60, 120, 120, 120, 120, 120, 120, 180, 180, 180, 180, 180, 180, 360, 360, - 360, 360, 360, 360, - 60, 60, 60, 60, 60, 60, 120, 120, 120, 120, 120, 120, 180, 180, 180, 180, 180, 180, 360, 360, - 360, 360, 360, 360, - 60, 60, 60, 60, 60, 60, 120, 120, 120, 120, 120, 120, 180, 180, 180, 180, 180, 180, 360, 360, - 360, 360, 360, 360, - 60, 60, 60, 60, 60, 60, 120, 120, 120, 120, 120, 120, 180, 180, 180, 180, 180, 180, 360, 360, - 360, 360, 360, 360, - 60, 60, 60, 60, 60, 60, 120, 120, 120, 120, 120, 120, 180, 180, 180, 180, 180, 180, 360, 360, - 360, 360, 360, 360, - 60, 60, 60, 60, 60, 60, 120, 120, 120, 120, 120, 120, 180, 180, 180, 180, 180, 180, 360, 360, - 360, 360, 360, 360, - 60, 60, 60, 60, 60, 60, 120, 120, 120, 120, 120, 120, 180, 180, 180, 180, 180, 180, 360, 360, - 360, 360, 360, 360, - 60, 60, 60, 60, 60, 60, 120, 120, 120, 120, 120, 120, 180, 180, 180, 180, 180, 180, 360, 360, - 360, 360, 360, 360, - 60, 60, 60, 60, 60, 60, 120, 120, 120, 120, 120, 120, 180, 180, 180, 180, 180, 180, 360, 360, - 360, 360, 360, 360, - ] + self.transmission_time_list = [ + 60, 90, 120, 180, 180, 180, 180, 180, 180, 360, 360, 360, 360, 360, 360, + 60, 90, 120, 180, 180, 180, 180, 180, 180, 360, 360, 360, 360, 360, 360, + 60, 90, 120, 180, 180, 180, 180, 180, 180, 360, 360, 360, 360, 360, 360, + 60, 90, 120, 180, 180, 180, 180, 180, 180, 360, 360, 360, 360, 360, 360, + 60, 90, 120, 180, 180, 180, 180, 180, 180, 360, 360, 360, 360, 360, 360, + 60, 90, 120, 180, 180, 180, 180, 180, 180, 360, 360, 360, 360, 360, 360, + 60, 90, 120, 180, 180, 180, 180, 180, 180, 360, 360, 360, 360, 360, 360, + 60, 90, 120, 180, 180, 180, 180, 180, 180, 360, 360, 360, 360, 360, 360, + 60, 90, 120, 180, 180, 180, 180, 180, 180, 360, 360, 360, 360, 360, 360, + 60, 90, 120, 180, 180, 180, 180, 180, 180, 360, 360, 360, 360, 360, 360, + 60, 90, 120, 180, 180, 180, 180, 180, 180, 360, 360, 360, 360, 360, 360, + 60, 90, 120, 180, 180, 180, 180, 180, 180, 360, 360, 360, 360, 360, 360, + 60, 90, 120, 180, 180, 180, 180, 180, 180, 360, 360, 360, 360, 360, 360, + 60, 90, 120, 180, 180, 180, 180, 180, 180, 360, 360, 360, 360, 360, 360, + 60, 90, 120, 180, 180, 180, 180, 180, 180, 360, 360, 360, 360, 360, 360, + 60, 90, 120, 180, 180, 180, 180, 180, 180, 360, 360, 360, 360, 360, 360, + 60, 90, 120, 180, 180, 180, 180, 180, 180, 360, 360, 360, 360, 360, 360, + 60, 90, 120, 180, 180, 180, 180, 180, 180, 360, 360, 360, 360, 360, 360, + 60, 90, 120, 180, 180, 180, 180, 180, 180, 360, 360, 360, 360, 360, 360, + 60, 90, 120, 180, 180, 180, 180, 180, 180, 360, 360, 360, 360, 360, 360, + 60, 90, 120, 180, 180, 180, 180, 180, 180, 360, 360, 360, 360, 360, 360, + 60, 90, 120, 180, 180, 180, 180, 180, 180, 360, 360, 360, 360, 360, 360, + 60, 90, 120, 180, 180, 180, 180, 180, 180, 360, 360, 360, 360, 360, 360, + 60, 90, 120, 180, 180, 180, 180, 180, 180, 360, 360, 360, 360, 360, 360, + 60, 90, 120, 180, 180, 180, 180, 180, 180, 360, 360, 360, 360, 360, 360 + ] # for testing only: self.transmission_time_list = [30, 30] self.signalling_max_attempts = len(self.transmission_time_list) @@ -453,62 +449,68 @@ class MeshRouter(): def add_mesh_ping_to_signalling_table(self, destination, origin, frametype, status): - timestamp = time.time() - #router = "" - #frametype = "PING" - payload = "" - attempt = 0 + try: + timestamp = time.time() + #router = "" + #frametype = "PING" + payload = "" + attempt = 0 - # [timestamp, destination, origin, frametype, payload, attempt, status] - # --------------0------------1---------2---------3--------4---------5--------6-----# - new_entry = [timestamp, destination, origin, frametype, payload, attempt, status] - for _, item in enumerate(MESH_SIGNALLING_TABLE): - # update entry if exists - if destination in item[1] and origin in item[2] and frametype in item[3]: - # reset attempts if entry exists and it failed or is acknowledged - attempt = 0 if item[6] in ["failed", "acknowledged"] else item[5] - update_entry = [item[0], destination, origin, frametype, "",attempt, status] - #print(f"UPDATE {MESH_SIGNALLING_TABLE[_]} >>> {update_entry}") + # [timestamp, destination, origin, frametype, payload, attempt, status] + # --------------0------------1---------2---------3--------4---------5--------6-----# + new_entry = [timestamp, destination, origin, frametype, payload, attempt, status] + for _, item in enumerate(MESH_SIGNALLING_TABLE): + # update entry if exists + if destination in item[1] and origin in item[2] and frametype in item[3]: + # reset attempts if entry exists and it failed or is acknowledged + attempt = 0 if item[6] in ["failed", "acknowledged"] else item[5] + update_entry = [item[0], destination, origin, frametype, "",attempt, status] + #print(f"UPDATE {MESH_SIGNALLING_TABLE[_]} >>> {update_entry}") - self.log.info(f"[MESH] [SIGNALLING TABLE] [UPDATE]: {MESH_SIGNALLING_TABLE[_]} >>> ", update=update_entry) + self.log.info(f"[MESH] [SIGNALLING TABLE] [UPDATE]: {MESH_SIGNALLING_TABLE[_]} >>> ", update=update_entry) - MESH_SIGNALLING_TABLE[_] = update_entry - return + MESH_SIGNALLING_TABLE[_] = update_entry + return - # add new routing entry if not exists - if new_entry not in MESH_SIGNALLING_TABLE: - #print(f"INSERT {new_entry} >>> SIGNALLING TABLE") - self.log.info("[MESH] [SIGNALLING TABLE] [INSERT]:", insert=new_entry) + # add new routing entry if not exists + if new_entry not in MESH_SIGNALLING_TABLE: + #print(f"INSERT {new_entry} >>> SIGNALLING TABLE") + self.log.info("[MESH] [SIGNALLING TABLE] [INSERT]:", insert=new_entry) - MESH_SIGNALLING_TABLE.append(new_entry) + MESH_SIGNALLING_TABLE.append(new_entry) + except Exception as e: + self.log.warning(f"[MESH] [SIGNALLING TABLE] [INSERT] [PING] [ERROR] ", e=e) def add_mesh_ping_ack_to_signalling_table(self, destination, origin, status): + try: + timestamp = time.time() + #router = "" + frametype = "PING-ACK" + payload = "" + attempt = 0 + new_entry = [timestamp, destination, origin, frametype, payload, attempt, status] - timestamp = time.time() - #router = "" - frametype = "PING-ACK" - payload = "" - attempt = 0 - new_entry = [timestamp, destination, origin, frametype, payload, attempt, status] + for _, item in enumerate(MESH_SIGNALLING_TABLE): + # update entry if exists + if destination in item[1] and origin in item[2] and item[3] in ["PING", "PING-ACK"]: + # reset attempts if entry exists and it failed or is acknowledged + attempt = 0 if item[6] in ["failed", "acknowledged"] else item[5] + update_entry = [item[0], destination, origin, "PING-ACK", "", attempt, status] + #print(f"UPDATE {MESH_SIGNALLING_TABLE[_]} >>> {update_entry}") + self.log.info(f"[MESH] [SIGNALLING TABLE] [UPDATE]: {MESH_SIGNALLING_TABLE[_]} >>> ", update=update_entry) - for _, item in enumerate(MESH_SIGNALLING_TABLE): - # update entry if exists - if destination in item[1] and origin in item[2] and item[3] in ["PING", "PING-ACK"]: - # reset attempts if entry exists and it failed or is acknowledged - attempt = 0 if item[6] in ["failed", "acknowledged"] else item[5] - update_entry = [item[0], destination, origin, "PING-ACK", "", attempt, status] - #print(f"UPDATE {MESH_SIGNALLING_TABLE[_]} >>> {update_entry}") - self.log.info(f"[MESH] [SIGNALLING TABLE] [UPDATE]: {MESH_SIGNALLING_TABLE[_]} >>> ", update=update_entry) + MESH_SIGNALLING_TABLE[_] = update_entry + return - MESH_SIGNALLING_TABLE[_] = update_entry - return + # add new routing entry if not exists + if new_entry not in MESH_SIGNALLING_TABLE: + #print(f"INSERT {new_entry} >>> SIGNALLING TABLE") + self.log.info(f"[MESH] [SIGNALLING TABLE] [INSERT] >>> ", update=new_entry) - # add new routing entry if not exists - if new_entry not in MESH_SIGNALLING_TABLE: - #print(f"INSERT {new_entry} >>> SIGNALLING TABLE") - self.log.info(f"[MESH] [SIGNALLING TABLE] [INSERT]: {MESH_SIGNALLING_TABLE[_]} >>> ", update=new_entry) + MESH_SIGNALLING_TABLE.append(new_entry) + except Exception as e: + self.log.warning(f"[MESH] [SIGNALLING TABLE] [INSERT] [PING ACK] [ERROR] ", e=e) - MESH_SIGNALLING_TABLE.append(new_entry) def enqueue_frame_for_tx( self, diff --git a/tnc/sock.py b/tnc/sock.py index 99420544..94ac8d1e 100644 --- a/tnc/sock.py +++ b/tnc/sock.py @@ -780,9 +780,16 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): raise TypeError binarydata = base64.b64decode(base64data) - + # check if hmac hash is provided + try: + log.info("[SCK] [HMAC] Looking for salt/token", local=mycallsign, remote=dxcallsign) + hmac_salt = helpers.get_hmac_salt(dxcallsign, mycallsign) + log.info("[SCK] [HMAC] Salt info", local=mycallsign, remote=dxcallsign, salt=hmac_salt) + except Exception: + log.warning("[SCK] [HMAC] No salt/token found") + hmac_salt = '' DATA_QUEUE_TRANSMIT.put( - ["ARQ_RAW", binarydata, arq_uuid, mycallsign, dxcallsign, attempts] + ["ARQ_RAW", binarydata, arq_uuid, mycallsign, dxcallsign, attempts, hmac_salt] ) except Exception as err: diff --git a/tnc/static.py b/tnc/static.py index c0534f19..6e6d3def 100644 --- a/tnc/static.py +++ b/tnc/static.py @@ -130,7 +130,7 @@ class TCIParam: @dataclass class TNC: - version = "0.10.2-alpha.1" + version = "0.10.3-alpha.1-hmac-exp4" host: str = "0.0.0.0" port: int = 3000 SOCKET_TIMEOUT: int = 1 # seconds @@ -144,6 +144,7 @@ class TNC: respond_to_call: bool = True # respond to cq, ping, connection request, file request if not in session heard_stations = [] listen: bool = True + enable_hmac: bool = True # ------------ diff --git a/tools/create_hmac_tokes.py b/tools/create_hmac_tokes.py new file mode 100755 index 00000000..f7c6bc71 --- /dev/null +++ b/tools/create_hmac_tokes.py @@ -0,0 +1,61 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +""" +Created on 26.08.23 + +@author: DJ2LS + +tool for generating HMAC tokens +""" + + +import argparse +import numpy as np + +def create_hmac_salts(dxcallsign: str, mycallsign: str, num_tokens: int): + """ + Creates a file with tokens for hmac signing + + Args: + dxcallsign: + mycallsign: + int: + + Returns: + bool + """ + try: + token_array = [] + for _ in range(num_tokens): + token_array.append(np.random.bytes(4).hex()) + + # Create and write random strings to a file + with open(f"freedata_hmac_STATION_{mycallsign}_REMOTE_{dxcallsign}.txt", "w") as file: + for _ in range(len(token_array)): + file.write(token_array[_] + '\n') + + # Create and write random strings to a file + with open(f"freedata_hmac_STATION_{dxcallsign}_REMOTE_{mycallsign}.txt", "w") as file: + for _ in range(len(token_array)): + file.write(token_array[_] + '\n') + + print("files created - place them in tnc/hmac folder and share the file with the remote station") + + except Exception: + print("error creating hmac file") + + +if __name__ == "__main__": + + parser = argparse.ArgumentParser(description='FreeDATA token generator') + + parser.add_argument('--dxcallsign', dest="dxcallsign", default='AA0AA', help="Select the destination callsign", type=str) + parser.add_argument('--mycallsign', dest="mycallsign", default='AA0AA', help="Select the own callsign", type=str) + parser.add_argument('--tokens', dest="tokens", default=1000, help="Amount of tokens to create", type=int) + + args = parser.parse_args() + create_hmac_salts(args.dxcallsign, args.mycallsign, int(args.tokens)) + + + +