Merge pull request #463 from DJ2LS/ls-hmac-signing

token based hmac signing

Merging this, but needs some updates later after VueJS PR has been merged
This commit is contained in:
DJ2LS 2023-09-27 11:24:07 +02:00 committed by GitHub
commit 89abba8150
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 429 additions and 102 deletions

View file

@ -80,6 +80,8 @@ PouchDB.plugin(require("pouchdb-find"));
//PouchDB.plugin(require('pouchdb-replication')); //PouchDB.plugin(require('pouchdb-replication'));
PouchDB.plugin(require("pouchdb-upsert")); PouchDB.plugin(require("pouchdb-upsert"));
var db = new PouchDB(chatDB); var db = new PouchDB(chatDB);
var users = new PouchDB(userDB); var users = new PouchDB(userDB);
@ -90,14 +92,15 @@ createUserIndex();
// REMOTE SYNC ATTEMPTS // REMOTE SYNC ATTEMPTS
//var remoteDB = new PouchDB('http://172.20.10.4:5984/chatDB') //var remoteDB = new PouchDB('http://172.20.10.4:5984/chatDB')
/* /*
// we need express packages for running pouchdb sync "express-pouchdb" // we need express packages for running pouchdb sync "express-pouchdb"
var express = require('express'); var express = require('express');
var app = express(); var app = express();
app.use('/chatDB', require('express-pouchdb')(PouchDB)); app.use('/', require('express-pouchdb')(PouchDB));
app.listen(5984); app.listen(5984);
var db = new PouchDB(chatDB);
app.use('/chatDB', require('pouchdb-express-router')(PouchDB)); app.use('/chatDB', require('pouchdb-express-router')(PouchDB));
@ -818,6 +821,7 @@ ipcRenderer.on("action-new-msg-received", (event, arg) => {
obj.checksum = "null"; obj.checksum = "null";
obj.msg = "null"; obj.msg = "null";
obj.status = item.status; obj.status = item.status;
obj.hmac_signed = item.hmac_signed;
obj.snr = item.snr; obj.snr = item.snr;
obj.type = "ping"; obj.type = "ping";
obj.filename = "null"; obj.filename = "null";
@ -897,6 +901,7 @@ ipcRenderer.on("action-new-msg-received", (event, arg) => {
obj.filetype = splitted_data[7]; obj.filetype = splitted_data[7];
//obj.file = btoa(splitted_data[8]); //obj.file = btoa(splitted_data[8]);
obj.file = FD.btoa_FD(splitted_data[8]); obj.file = FD.btoa_FD(splitted_data[8]);
obj.hmac_signed = item.hmac_signed;
obj.new = 1; obj.new = 1;
} else if (splitted_data[1] == "req" && splitted_data[2] == "0") { } else if (splitted_data[1] == "req" && splitted_data[2] == "0") {
obj.uuid = uuidv4().toString(); obj.uuid = uuidv4().toString();
@ -910,6 +915,7 @@ ipcRenderer.on("action-new-msg-received", (event, arg) => {
obj.filename = "null"; obj.filename = "null";
obj.filetype = "null"; obj.filetype = "null";
obj.file = "null"; obj.file = "null";
obj.hmac_signed = item.hmac_signed;
obj.new = 0; obj.new = 0;
if (config.enable_request_profile == "True") { if (config.enable_request_profile == "True") {
sendUserData(item.dxcallsign); sendUserData(item.dxcallsign);
@ -926,6 +932,7 @@ ipcRenderer.on("action-new-msg-received", (event, arg) => {
obj.filename = "null"; obj.filename = "null";
obj.filetype = "null"; obj.filetype = "null";
obj.file = "null"; obj.file = "null";
obj.hmac_signed = item.hmac_signed;
obj.new = 0; obj.new = 0;
if (config.enable_request_shared_folder == "True") { if (config.enable_request_shared_folder == "True") {
sendSharedFolderList(item.dxcallsign); sendSharedFolderList(item.dxcallsign);
@ -947,6 +954,7 @@ ipcRenderer.on("action-new-msg-received", (event, arg) => {
obj.filename = "null"; obj.filename = "null";
obj.filetype = "null"; obj.filetype = "null";
obj.file = "null"; obj.file = "null";
obj.hmac_signed = item.hmac_signed;
obj.new = 0; obj.new = 0;
if (config.enable_request_shared_folder == "True") { if (config.enable_request_shared_folder == "True") {
sendSharedFolderFile(item.dxcallsign, name); sendSharedFolderFile(item.dxcallsign, name);
@ -963,6 +971,7 @@ ipcRenderer.on("action-new-msg-received", (event, arg) => {
obj.filename = "null"; obj.filename = "null";
obj.filetype = "null"; obj.filetype = "null";
obj.file = "null"; obj.file = "null";
obj.hmac_signed = item.hmac_signed;
obj.new = 0; obj.new = 0;
console.log(splitted_data); console.log(splitted_data);
let userData = new Object(); let userData = new Object();
@ -992,6 +1001,7 @@ ipcRenderer.on("action-new-msg-received", (event, arg) => {
obj.filename = "null"; obj.filename = "null";
obj.filetype = "null"; obj.filetype = "null";
obj.file = "null"; obj.file = "null";
obj.hmac_signed = item.hmac_signed;
obj.new = 0; obj.new = 0;
console.log(splitted_data); console.log(splitted_data);
@ -1006,7 +1016,7 @@ ipcRenderer.on("action-new-msg-received", (event, arg) => {
//getSetUserInformation(selected_callsign); //getSetUserInformation(selected_callsign);
} else if (splitted_data[1] == "res-2") { } 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); let sharedFileInfo = splitted_data[2].split("/", 2);
obj.uuid = uuidv4().toString(); obj.uuid = uuidv4().toString();
@ -1020,6 +1030,7 @@ ipcRenderer.on("action-new-msg-received", (event, arg) => {
obj.filename = sharedFileInfo[0]; obj.filename = sharedFileInfo[0];
obj.filetype = "application/octet-stream"; obj.filetype = "application/octet-stream";
obj.file = FD.btoa_FD(sharedFileInfo[1]); obj.file = FD.btoa_FD(sharedFileInfo[1]);
obj.hmac_signed = item.hmac_signed;
obj.new = 0; obj.new = 0;
} else { } else {
console.log("no rule matched for handling received data!"); console.log("no rule matched for handling received data!");
@ -1049,16 +1060,13 @@ update_chat = function (obj) {
} }
return doc; return doc;
}); });
obj.attempt = 1;
}
// define attempts
if (typeof obj.attempt == "undefined") {
var attempt = 1; var attempt = 1;
obj.attempt = attempt;
} else { } else {
var attempt = obj.attempt; var attempt = obj.attempt;
} }
// add percent and bytes per minute if not existing // add percent and bytes per minute if not existing
//console.log(obj.percent) //console.log(obj.percent)
if (typeof obj.percent == "undefined") { if (typeof obj.percent == "undefined") {
@ -1387,6 +1395,18 @@ update_chat = function (obj) {
showOsPopUp("Message received from " + obj.dxcallsign, obj.msg); 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 = '<i class="bi bi-shield-fill-check"></i>';
} else {
var hmac_signed = '<i class="bi bi-shield-x"></i>';
}
var new_message = ` var new_message = `
<div class="d-flex align-items-center" style="margin-left: auto;"> <!-- max-width: 75%; --> <div class="d-flex align-items-center" style="margin-left: auto;"> <!-- max-width: 75%; -->
@ -1401,6 +1421,17 @@ update_chat = function (obj) {
<span class="badge bg-light text-muted">${timestamp}</span> <span class="badge bg-light text-muted">${timestamp}</span>
</p> </p>
<span id="msg-${
obj._id
}-hmac-badge" class="position-absolute top-0 start-100 translate-middle badge rounded-1 bg-secondary border border-white">
<span id="msg-${
obj._id
}-hmac-signed" class="">${hmac_signed}</span>
</span>
</div> </div>
</div> </div>
</div> </div>
@ -1925,6 +1956,7 @@ add_obj_to_database = function (obj) {
status: obj.status, status: obj.status,
snr: obj.snr, snr: obj.snr,
attempt: obj.attempt, attempt: obj.attempt,
hmac_signed: obj.hmac_signed,
new: obj.new, new: obj.new,
_attachments: { _attachments: {
[obj.filename]: { [obj.filename]: {
@ -2173,6 +2205,7 @@ function createChatIndex() {
"status", "status",
"percent", "percent",
"attempt", "attempt",
"hmac_signed",
"bytesperminute", "bytesperminute",
"_attachments", "_attachments",
"new", "new",

View file

@ -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=invalid-name, line-too-long, c-extension-no-member
# pylint: disable=import-outside-toplevel, attribute-defined-outside-init # pylint: disable=import-outside-toplevel, attribute-defined-outside-init
# pylint: disable=fixme
import os import os
import base64 import base64
@ -15,7 +17,8 @@ import time
import uuid import uuid
import lzma import lzma
from random import randrange from random import randrange
import hmac
import hashlib
import codec2 import codec2
import helpers import helpers
import modem import modem
@ -326,7 +329,8 @@ class DATA:
# [3] mycallsign with ssid # [3] mycallsign with ssid
# [4] dxcallsign with ssid # [4] dxcallsign with ssid
# [5] attempts # [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": elif data[0] == "FEC_IS_WRITING":
@ -915,9 +919,27 @@ class DATA:
data_frame = payload[9:] data_frame = payload[9:]
data_frame_crc_received = helpers.get_crc_32(data_frame) data_frame_crc_received = helpers.get_crc_32(data_frame)
# Check if data_frame_crc is equal with received crc # check if hmac signing enabled
if data_frame_crc == data_frame_crc_received: if TNC.enable_hmac:
self.arq_process_received_data_frame(data_frame, snr) 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: else:
self.send_data_to_socket_queue( self.send_data_to_socket_queue(
freedata="tnc-message", freedata="tnc-message",
@ -1019,18 +1041,21 @@ class DATA:
# Update modes we are listening to # Update modes we are listening to
self.set_listening_modes(False, True, self.mode_list[self.speed_level]) 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 # transmittion duration
signed = "True" if signed else "False"
duration = time.time() - self.rx_start_of_transmission duration = time.time() - self.rx_start_of_transmission
self.calculate_transfer_rate_rx( self.calculate_transfer_rate_rx(
self.rx_start_of_transmission, len(ARQ.rx_frame_buffer) 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, 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 # Decompress the data frame
data_frame_decompressed = lzma.decompress(data_frame) data_frame_decompressed = lzma.decompress(data_frame)
@ -1125,7 +1150,8 @@ class DATA:
dxcallsign=str(Station.dxcallsign, "UTF-8"), dxcallsign=str(Station.dxcallsign, "UTF-8"),
dxgrid=str(Station.dxgrid, "UTF-8"), dxgrid=str(Station.dxgrid, "UTF-8"),
data=base64_data, 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: if TNC.enable_stats:
@ -1150,7 +1176,7 @@ class DATA:
snr=snr, snr=snr,
) )
def arq_transmit(self, data_out: bytes): def arq_transmit(self, data_out: bytes, hmac_salt: bytes):
""" """
Transmit ARQ frame Transmit ARQ frame
@ -1204,6 +1230,16 @@ class DATA:
tx_start_of_transmission = time.time() tx_start_of_transmission = time.time()
self.calculate_transfer_rate_tx(tx_start_of_transmission, 0, len(data_out)) self.calculate_transfer_rate_tx(tx_start_of_transmission, 0, len(data_out))
# 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 # Append a crc at the beginning and end of file indicators
frame_payload_crc = helpers.get_crc_32(data_out) frame_payload_crc = helpers.get_crc_32(data_out)
self.log.debug("[TNC] frame payload CRC:", crc=frame_payload_crc.hex()) self.log.debug("[TNC] frame payload CRC:", crc=frame_payload_crc.hex())
@ -1269,7 +1305,7 @@ class DATA:
tempbuffer = [] tempbuffer = []
self.rpt_request_buffer = [] self.rpt_request_buffer = []
# Append data frames with n_frames_per_burst to tempbuffer # 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 = bytearray()
arqheader[:1] = bytes([FR_TYPE.BURST_01.value + n_frame]) arqheader[:1] = bytes([FR_TYPE.BURST_01.value + n_frame])
#####arqheader[:1] = bytes([FR_TYPE.BURST_01.value]) #####arqheader[:1] = bytes([FR_TYPE.BURST_01.value])
@ -1643,7 +1679,7 @@ class DATA:
print(self.rpt_request_buffer) print(self.rpt_request_buffer)
tempbuffer_rptframes = [] tempbuffer_rptframes = []
for i in range(0, len(missing_area)): for i in range(len(missing_area)):
print(missing_area[i]) print(missing_area[i])
missing_frames_buffer_position = missing_area[i] - 1 missing_frames_buffer_position = missing_area[i] - 1
tempbuffer_rptframes.append(self.rpt_request_buffer[missing_frames_buffer_position]) tempbuffer_rptframes.append(self.rpt_request_buffer[missing_frames_buffer_position])
@ -2061,6 +2097,7 @@ class DATA:
mycallsign, mycallsign,
dxcallsign, dxcallsign,
attempts: int, attempts: int,
hmac_salt: str
) -> bool: ) -> bool:
""" """
Open data channel and transmit data Open data channel and transmit data
@ -2109,7 +2146,7 @@ class DATA:
threading.Event().wait(0.01) threading.Event().wait(0.01)
if ARQ.arq_state: if ARQ.arq_state:
self.arq_transmit(data_out) self.arq_transmit(data_out, hmac_salt)
return True return True
return False return False

View file

@ -13,6 +13,13 @@ import structlog
import numpy as np import numpy as np
import threading import threading
import mesh import mesh
import hashlib
import hmac
import os
import sys
from pathlib import Path
log = structlog.get_logger("helpers") log = structlog.get_logger("helpers")
@ -489,3 +496,172 @@ def return_key_from_object(default, obj, key):
def bool_to_string(state): def bool_to_string(state):
return "True" if state else "False" 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

View file

@ -253,6 +253,14 @@ if __name__ == "__main__":
help="Enable mesh protocol", 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() ARGS = PARSER.parse_args()
# set save to folder state for allowing downloading files to local file system # 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 TCIParam.port = ARGS.tci_port
ModemParam.tx_delay = ARGS.tx_delay ModemParam.tx_delay = ARGS.tx_delay
MeshParam.enable_protocol = ARGS.enable_mesh MeshParam.enable_protocol = ARGS.enable_mesh
TNC.enable_hmac = ARGS.enable_hmac
except Exception as e: except Exception as e:
log.error("[DMN] Error reading config file", exception=e) log.error("[DMN] Error reading config file", exception=e)

View file

@ -53,36 +53,32 @@ class MeshRouter():
self.log = structlog.get_logger("RF") 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, self.transmission_time_list = [
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, 90, 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, 90, 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, 90, 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, 90, 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, 90, 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, 90, 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, 90, 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, 90, 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, 90, 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, 60, 90, 120, 180, 180, 180, 180, 180, 180, 360, 360, 360, 360, 360, 360,
360, 360, 360, 360, 60, 90, 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, 60, 90, 120, 180, 180, 180, 180, 180, 180, 360, 360, 360, 360, 360, 360,
360, 360, 360, 360, 60, 90, 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, 60, 90, 120, 180, 180, 180, 180, 180, 180, 360, 360, 360, 360, 360, 360,
360, 360, 360, 360, 60, 90, 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, 60, 90, 120, 180, 180, 180, 180, 180, 180, 360, 360, 360, 360, 360, 360,
360, 360, 360, 360, 60, 90, 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, 60, 90, 120, 180, 180, 180, 180, 180, 180, 360, 360, 360, 360, 360, 360,
360, 360, 360, 360, 60, 90, 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, 60, 90, 120, 180, 180, 180, 180, 180, 180, 360, 360, 360, 360, 360, 360,
360, 360, 360, 360, 60, 90, 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, 60, 90, 120, 180, 180, 180, 180, 180, 180, 360, 360, 360, 360, 360, 360,
360, 360, 360, 360, 60, 90, 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, 60, 90, 120, 180, 180, 180, 180, 180, 180, 360, 360, 360, 360, 360, 360,
360, 360, 360, 360, 60, 90, 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,
] ]
# for testing only: self.transmission_time_list = [30, 30] # for testing only: self.transmission_time_list = [30, 30]
self.signalling_max_attempts = len(self.transmission_time_list) self.signalling_max_attempts = len(self.transmission_time_list)
@ -453,6 +449,7 @@ class MeshRouter():
def add_mesh_ping_to_signalling_table(self, destination, origin, frametype, status): def add_mesh_ping_to_signalling_table(self, destination, origin, frametype, status):
try:
timestamp = time.time() timestamp = time.time()
#router = "" #router = ""
#frametype = "PING" #frametype = "PING"
@ -482,8 +479,10 @@ class MeshRouter():
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): def add_mesh_ping_ack_to_signalling_table(self, destination, origin, status):
try:
timestamp = time.time() timestamp = time.time()
#router = "" #router = ""
frametype = "PING-ACK" frametype = "PING-ACK"
@ -506,9 +505,12 @@ class MeshRouter():
# add new routing entry if not exists # add new routing entry if not exists
if new_entry not in MESH_SIGNALLING_TABLE: if new_entry not in MESH_SIGNALLING_TABLE:
#print(f"INSERT {new_entry} >>> SIGNALLING TABLE") #print(f"INSERT {new_entry} >>> SIGNALLING TABLE")
self.log.info(f"[MESH] [SIGNALLING TABLE] [INSERT]: {MESH_SIGNALLING_TABLE[_]} >>> ", update=new_entry) self.log.info(f"[MESH] [SIGNALLING TABLE] [INSERT] >>> ", update=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 ACK] [ERROR] ", e=e)
def enqueue_frame_for_tx( def enqueue_frame_for_tx(
self, self,

View file

@ -780,9 +780,16 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler):
raise TypeError raise TypeError
binarydata = base64.b64decode(base64data) 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( 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: except Exception as err:

View file

@ -130,7 +130,7 @@ class TCIParam:
@dataclass @dataclass
class TNC: class TNC:
version = "0.10.2-alpha.1" version = "0.10.3-alpha.1-hmac-exp4"
host: str = "0.0.0.0" host: str = "0.0.0.0"
port: int = 3000 port: int = 3000
SOCKET_TIMEOUT: int = 1 # seconds 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 respond_to_call: bool = True # respond to cq, ping, connection request, file request if not in session
heard_stations = [] heard_stations = []
listen: bool = True listen: bool = True
enable_hmac: bool = True
# ------------ # ------------

61
tools/create_hmac_tokes.py Executable file
View file

@ -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))