From a4c6fe92196cac9bf8291fe40481edb4e751122d Mon Sep 17 00:00:00 2001 From: israel Date: Mon, 16 Oct 2023 11:37:24 +0300 Subject: [PATCH 01/79] working basic dtls --- src/mainwindow.ui | 24 ++++++++++++++---------- src/packet.cpp | 6 ++++++ src/packet.h | 1 + src/packetnetwork.cpp | 35 +++++++++++++++++++++++++++++++++++ src/packetnetwork.h | 2 ++ 5 files changed, 58 insertions(+), 10 deletions(-) diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 48ed25e2..a3836c68 100755 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -236,34 +236,39 @@ - UDP + HTTPS Post - :/icons/tx_udp.png:/icons/tx_udp.png + :/icons/tx_http.png:/icons/tx_http.png - SSL + DTLS + + + + + UDP - :/icons/tx_ssl.png:/icons/tx_ssl.png + :/icons/tx_udp.png:/icons/tx_udp.png - HTTP Get + SSL - :/icons/tx_http.png:/icons/tx_http.png + :/icons/tx_ssl.png:/icons/tx_ssl.png - HTTP Post + HTTP Get @@ -272,7 +277,7 @@ - HTTPS Get + HTTP Post @@ -281,7 +286,7 @@ - HTTPS Post + HTTPS Get @@ -353,7 +358,6 @@ - 75 true diff --git a/src/packet.cpp b/src/packet.cpp index c46fbf8e..830c0dd3 100755 --- a/src/packet.cpp +++ b/src/packet.cpp @@ -60,6 +60,12 @@ void Packet::clear() init(); } +bool Packet::isDTLS() +{ + return ((tcpOrUdp.trimmed().toLower() == "dtls")); +} + + bool Packet::isSSL() { return (tcpOrUdp.trimmed().toLower().contains("ssl")); diff --git a/src/packet.h b/src/packet.h index 5e3a6ce5..0089a9a4 100755 --- a/src/packet.h +++ b/src/packet.h @@ -62,6 +62,7 @@ class Packet bool incoming; void init(); void clear(); + bool isDTLS(); bool isTCP(); bool isSSL(); bool isUDP(); diff --git a/src/packetnetwork.cpp b/src/packetnetwork.cpp index 4c5272bf..607fdf22 100755 --- a/src/packetnetwork.cpp +++ b/src/packetnetwork.cpp @@ -822,6 +822,41 @@ void PacketNetwork::packetToSend(Packet sendpacket) sendpacket.timestamp = QDateTime::currentDateTime(); sendpacket.name = sendpacket.timestamp.toString(DATETIMEFORMAT); + if(sendpacket.isDTLS()){ + // QUdpSocket * sendUDP; + // sendUDP = new QUdpSocket(this); + // sendUDP->bind(sendpacket.port); + // QHostAddress resolved = resolveDNS(sendpacket.toIP); + // sendUDP->writeDatagram(sendpacket.getByteArray().append("333333"), resolved, sendpacket.port); + // emit packetSent(sendpacket); + // Replace "your_executable.exe" with the actual path to the executable file + + //const char* executablePath = "C:/OpenSSL/bin/openssl.exe"; + //const char* arguments = "s_client -dtls1_2 -connect localhost:12345 -key C:/Users/ISRAELS4/Desktop/new openssl certificate/11.9.23-dtls/client-key.pem -cert C:/Users/ISRAELS4/Desktop/new openssl certificate/11.9.23-dtls/client-signed-cert.pem"; + //HINSTANCE hInstance = ShellExecuteA(NULL, "open", executablePath, arguments, NULL, SW_SHOWNORMAL); + + // Specify the command with arguments + //const char* command = "cmd openssl s_client -dtls1_2 -connect localhost:12345 -key C:/Users/ISRAELS4/Desktop/new openssl certificate/11.9.23-dtls/client-key.pem -cert C:/Users/ISRAELS4/Desktop/new openssl certificate/11.9.23-dtls/client-signed-cert.pem"; + + // Use the system function to run the command + //"your_command_here \"C:\\Users\\YourUsername\\YourFile.txt\""; + //command && echo input | command + QByteArray data = sendpacket.getByteArray(); + const char* opensslPath; + int isSessionOpen= 0; + if (!isSessionOpen){ + isSessionOpen = 1; + system("type nul > session.pem"); + opensslPath ="echo "+ data +" |openssl s_client -dtls1_2 -connect localhost:12345 -sess_out session.pem"; + system(opensslPath); + } else{ + opensslPath ="echo "+ data +" |openssl s_client -dtls1_2 -connect localhost:12345 -sess_in session.pem"; + system(opensslPath); + } + + emit packetSent(sendpacket); + } + if (sendpacket.isUDP()) { QUdpSocket * sendUDP; bool oneoff = false; diff --git a/src/packetnetwork.h b/src/packetnetwork.h index 2a30d066..fcf4fb9b 100755 --- a/src/packetnetwork.h +++ b/src/packetnetwork.h @@ -31,6 +31,8 @@ #endif #include + + class PacketNetwork : public QObject { Q_OBJECT From c406a827ca40b6a0472c05133b5e521e61449b29 Mon Sep 17 00:00:00 2001 From: israel Date: Mon, 16 Oct 2023 17:24:06 +0300 Subject: [PATCH 02/79] make dtls session stable --- src/packetnetwork.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/packetnetwork.cpp b/src/packetnetwork.cpp index 607fdf22..91ca3eda 100755 --- a/src/packetnetwork.cpp +++ b/src/packetnetwork.cpp @@ -843,9 +843,9 @@ void PacketNetwork::packetToSend(Packet sendpacket) //command && echo input | command QByteArray data = sendpacket.getByteArray(); const char* opensslPath; - int isSessionOpen= 0; + static int isSessionOpen = false; if (!isSessionOpen){ - isSessionOpen = 1; + isSessionOpen = true; system("type nul > session.pem"); opensslPath ="echo "+ data +" |openssl s_client -dtls1_2 -connect localhost:12345 -sess_out session.pem"; system(opensslPath); From 57a9253f328a700f81bd393deb55ed49adabe68b Mon Sep 17 00:00:00 2001 From: israel Date: Tue, 17 Oct 2023 13:50:18 +0300 Subject: [PATCH 03/79] add the lineEdit only if dtls has chosen --- src/mainwindow.cpp | 12 +++++++++++- src/mainwindow.ui | 3 +++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index da82e430..62ebae38 100755 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -68,7 +68,9 @@ MainWindow::MainWindow(QWidget *parent) : ui(new Ui::MainWindow) { ui->setupUi(this); - + if ( ui->udptcpComboBox->currentText().toLower() != "dtls"){ + ui->pathToKeyLineEdit->hide(); + } QSettings settings(SETTINGSFILE, QSettings::IniFormat); @@ -2588,6 +2590,14 @@ void MainWindow::on_udptcpComboBox_currentIndexChanged(const QString &arg1) auto isHttp = selectedText.contains("http"); auto isPost = selectedText.contains("post") && isHttp; + /////////////////////////////////dtls add line edit for adding path for cert + + if ( ui->udptcpComboBox->currentText().toLower() == "dtls") { + ui->pathToKeyLineEdit->show(); // Enable when "dtls" is selected + } else { + ui->pathToKeyLineEdit->hide(); // Disable for other options + } + if(isHttp) { ui->asciiLabel->setText("Data"); } else { diff --git a/src/mainwindow.ui b/src/mainwindow.ui index a3836c68..48f4263b 100755 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -313,6 +313,9 @@ + + + From 1195e2407aac73b46d34f2fe054f41f390c37614 Mon Sep 17 00:00:00 2001 From: israel Date: Tue, 17 Oct 2023 17:23:17 +0300 Subject: [PATCH 04/79] add the option to add key and cert --- src/mainwindow.cpp | 71 +++++++++++++++++++++++++++++++++++++++++-- src/mainwindow.h | 7 ++++- src/mainwindow.ui | 48 ++++++++++++++++++++++++++++- src/packetnetwork.cpp | 2 +- src/packetnetwork.h | 2 ++ 5 files changed, 124 insertions(+), 6 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 62ebae38..ce702924 100755 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -67,11 +68,18 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { + ui->setupUi(this); + if ( ui->udptcpComboBox->currentText().toLower() != "dtls"){ - ui->pathToKeyLineEdit->hide(); + ui->loadKeyButton->hide(); + ui->loadCertButton->hide(); } + connect(loadKeyButton, &QPushButton::clicked, this, &MainWindow::on_loadKeyButton_clicked); + connect(loadCertButton, &QPushButton::clicked, this, &MainWindow::on_loadCertButton_clicked); + + QSettings settings(SETTINGSFILE, QSettings::IniFormat); QIcon mIcon(":pslogo.png"); @@ -2576,6 +2584,60 @@ void MainWindow::on_loadFileButton_clicked() } +void MainWindow::on_loadKeyButton_clicked() +{ + static QString fileName; + static bool showWarning = true; + + if (fileName.isEmpty()) { + fileName = QDir::homePath(); + } + + fileName = QFileDialog::getOpenFileName(this, tr("Import File"), + fileName, + tr("*.*")); + + QDEBUGVAR(fileName); + + if (fileName.isEmpty()) { + QMessageBox::critical(this, "Error", "The uploaded file is empty."); + return; + // + } + + packetNetwork.keyPath = fileName.toUtf8(); + + //QByteArray data = filename; + +} + +void MainWindow::on_loadCertButton_clicked() +{ + static QString fileName; + static bool showWarning = true; + + if (fileName.isEmpty()) { + fileName = QDir::homePath(); + } + + fileName = QFileDialog::getOpenFileName(this, tr("Import File"), + fileName, + tr("*.*")); + + QDEBUGVAR(fileName); + + if (fileName.isEmpty()) { + QMessageBox::critical(this, "Error", "The uploaded file is empty."); + return; + // + } + + packetNetwork.certPath = fileName.toUtf8(); + + //QByteArray data = filename; + +} + void MainWindow::on_actionDonate_Thank_You_triggered() { @@ -2593,11 +2655,14 @@ void MainWindow::on_udptcpComboBox_currentIndexChanged(const QString &arg1) /////////////////////////////////dtls add line edit for adding path for cert if ( ui->udptcpComboBox->currentText().toLower() == "dtls") { - ui->pathToKeyLineEdit->show(); // Enable when "dtls" is selected + ui->loadKeyButton->show(); // Enable when "dtls" is selected + ui->loadCertButton->show(); } else { - ui->pathToKeyLineEdit->hide(); // Disable for other options + ui->loadKeyButton->hide(); // Disable for other options + ui->loadCertButton->hide(); // Disable for other options } + if(isHttp) { ui->asciiLabel->setText("Data"); } else { diff --git a/src/mainwindow.h b/src/mainwindow.h index 4da01674..c640c397 100755 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -77,7 +77,8 @@ class MainWindow : public QMainWindow void loadPacketsTable(); - + QPushButton *loadKeyButton; + QPushButton *loadCertButton; QPushButton *generatePSLink(); QPushButton *generateDNLink(); void populateTableRow(int rowCounter, Packet tempPacket); @@ -210,6 +211,10 @@ class MainWindow : public QMainWindow void on_loadFileButton_clicked(); + void on_loadKeyButton_clicked(); + + void on_loadCertButton_clicked(); + void on_actionDonate_Thank_You_triggered(); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 48f4263b..61861f48 100755 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -314,7 +314,51 @@ - + + + true + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + + + 90 + 0 + 111 + 21 + + + + Load Certificate File + + + + + + 0 + 0 + 80 + 21 + + + + Load Key File + + + loadKeyButton + loadCertButton + @@ -512,6 +556,8 @@ + packetSetupGroup + splitter diff --git a/src/packetnetwork.cpp b/src/packetnetwork.cpp index 91ca3eda..916dce13 100755 --- a/src/packetnetwork.cpp +++ b/src/packetnetwork.cpp @@ -847,7 +847,7 @@ void PacketNetwork::packetToSend(Packet sendpacket) if (!isSessionOpen){ isSessionOpen = true; system("type nul > session.pem"); - opensslPath ="echo "+ data +" |openssl s_client -dtls1_2 -connect localhost:12345 -sess_out session.pem"; + opensslPath ="echo "+ data +" |openssl s_client -dtls1_2 -connect localhost:12345 -sess_out session.pem -key " + keyPath + " -cert " + certPath; system(opensslPath); } else{ opensslPath ="echo "+ data +" |openssl s_client -dtls1_2 -connect localhost:12345 -sess_in session.pem"; diff --git a/src/packetnetwork.h b/src/packetnetwork.h index fcf4fb9b..3ac3b42f 100755 --- a/src/packetnetwork.h +++ b/src/packetnetwork.h @@ -37,6 +37,8 @@ class PacketNetwork : public QObject { Q_OBJECT public: + QByteArray keyPath; + QByteArray certPath; explicit PacketNetwork(QObject *parent = nullptr); void init(); From c7308b4d41ca70587aef898d42ef50589fca2726 Mon Sep 17 00:00:00 2001 From: israel Date: Tue, 17 Oct 2023 18:00:40 +0300 Subject: [PATCH 05/79] add key&cert option working and permanent connection --- src/packetnetwork.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/packetnetwork.cpp b/src/packetnetwork.cpp index 916dce13..fb96c3e3 100755 --- a/src/packetnetwork.cpp +++ b/src/packetnetwork.cpp @@ -842,16 +842,21 @@ void PacketNetwork::packetToSend(Packet sendpacket) //"your_command_here \"C:\\Users\\YourUsername\\YourFile.txt\""; //command && echo input | command QByteArray data = sendpacket.getByteArray(); - const char* opensslPath; + QByteArray opensslPath; static int isSessionOpen = false; if (!isSessionOpen){ isSessionOpen = true; system("type nul > session.pem"); opensslPath ="echo "+ data +" |openssl s_client -dtls1_2 -connect localhost:12345 -sess_out session.pem -key " + keyPath + " -cert " + certPath; - system(opensslPath); + const char* charArray = opensslPath.data(); + int status = system(charArray); + if (status!=0){//if the connection doesn't established + isSessionOpen = false; + } } else{ opensslPath ="echo "+ data +" |openssl s_client -dtls1_2 -connect localhost:12345 -sess_in session.pem"; - system(opensslPath); + const char* charArray2=opensslPath.data(); + system(charArray2); } emit packetSent(sendpacket); From c51aa9b17eda9837ded42eb1da476299b7c38e01 Mon Sep 17 00:00:00 2001 From: israel Date: Wed, 18 Oct 2023 19:40:59 +0300 Subject: [PATCH 06/79] add the hide mode cmd with reused session id --- src/packetnetwork.cpp | 56 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/src/packetnetwork.cpp b/src/packetnetwork.cpp index fb96c3e3..501e83cb 100755 --- a/src/packetnetwork.cpp +++ b/src/packetnetwork.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #ifdef CONSOLE_BUILD class QMessageBox { public: @@ -842,23 +843,62 @@ void PacketNetwork::packetToSend(Packet sendpacket) //"your_command_here \"C:\\Users\\YourUsername\\YourFile.txt\""; //command && echo input | command QByteArray data = sendpacket.getByteArray(); + DWORD status; + QByteArray opensslPath; static int isSessionOpen = false; if (!isSessionOpen){ isSessionOpen = true; - system("type nul > session.pem"); - opensslPath ="echo "+ data +" |openssl s_client -dtls1_2 -connect localhost:12345 -sess_out session.pem -key " + keyPath + " -cert " + certPath; - const char* charArray = opensslPath.data(); - int status = system(charArray); + opensslPath ="cmd.exe /c (type nul > session.pem) & (echo "+ data + " | openssl s_client -dtls1_2 -connect localhost:12345 -sess_out session.pem -key " + keyPath + " -cert " + certPath +")"; + QString qstr = QString::fromUtf8(opensslPath); + std::wstring wstr = qstr.toStdWString(); + LPWSTR lpwstr = &wstr[0]; + STARTUPINFO si; + PROCESS_INFORMATION pi; + + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + ZeroMemory(&pi, sizeof(pi)); + + // Create the process in hidden mode + if (CreateProcess(NULL, lpwstr, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) { + WaitForSingleObject(pi.hProcess, 10000); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } else { + // Handle an error if CreateProcess fails + DWORD error = GetLastError(); + printf("CreateProcess failed (%d)\n", GetLastError()); + + } + GetExitCodeProcess(pi.hProcess, &status); if (status!=0){//if the connection doesn't established isSessionOpen = false; } } else{ - opensslPath ="echo "+ data +" |openssl s_client -dtls1_2 -connect localhost:12345 -sess_in session.pem"; - const char* charArray2=opensslPath.data(); - system(charArray2); - } + opensslPath ="cmd.exe /c echo "+ data + " | openssl s_client -dtls1_2 -connect localhost:12345 -sess_in session.pem"; + QString qstr = QString::fromUtf8(opensslPath); + std::wstring wstr = qstr.toStdWString(); + LPWSTR lpwstr = &wstr[0]; + STARTUPINFO si; + PROCESS_INFORMATION pi; + + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + ZeroMemory(&pi, sizeof(pi)); + + // Create the process in hidden mode + if (CreateProcess(NULL, lpwstr, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) { + WaitForSingleObject(pi.hProcess, 10000); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } else { + // Handle an error if CreateProcess fails + DWORD error = GetLastError(); + printf("CreateProcess failed (%d)\n", GetLastError()); + } + } emit packetSent(sendpacket); } From 009dc2b0c835c2de33b4a43146af0c966197e027 Mon Sep 17 00:00:00 2001 From: israel Date: Wed, 18 Oct 2023 20:30:11 +0300 Subject: [PATCH 07/79] added hide mode cmd --- src/packetnetwork.cpp | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/packetnetwork.cpp b/src/packetnetwork.cpp index 501e83cb..ded5da1f 100755 --- a/src/packetnetwork.cpp +++ b/src/packetnetwork.cpp @@ -899,6 +899,29 @@ void PacketNetwork::packetToSend(Packet sendpacket) } } +// QByteArray opensslPath="cmd.exe /c echo " + data +" |openssl s_client -dtls1_2 -connect localhost:12345 -sess_out session.pem -key " + keyPath + " -cert " + certPath; +// QString qstr = QString::fromUtf8(opensslPath); +// std::wstring wstr = qstr.toStdWString(); +// LPWSTR lpwstr = &wstr[0]; +// STARTUPINFO si; +// PROCESS_INFORMATION pi; + +// ZeroMemory(&si, sizeof(si)); +// si.cb = sizeof(si); +// ZeroMemory(&pi, sizeof(pi)); + +// // Create the process in hidden mode +// if (CreateProcess(NULL, lpwstr, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) { +// WaitForSingleObject(pi.hProcess, 5000); +// CloseHandle(pi.hProcess); +// CloseHandle(pi.hThread); +// } else { +// // Handle an error if CreateProcess fails +// DWORD error = GetLastError(); +// int errnum = GetLastError(); +// printf("CreateProcess failed (%d)\n", GetLastError()); + +// } emit packetSent(sendpacket); } From 062bf0eb4a637b84e1222c3f2632106a0069f0fc Mon Sep 17 00:00:00 2001 From: israel Date: Thu, 19 Oct 2023 11:00:54 +0300 Subject: [PATCH 08/79] view the chosen key and cert on buttons --- src/globals.h | 2 +- src/mainwindow.cpp | 17 +++++++++++++++-- src/mainwindow.ui | 20 ++++++++++++++++++-- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/globals.h b/src/globals.h index 30a520b5..483ce9e4 100755 --- a/src/globals.h +++ b/src/globals.h @@ -49,7 +49,7 @@ #define PANELSFILE ((QFile::exists("ps_panels.json") || QFile::exists("portablemode.txt") ) ? ("ps_panels.json") : ((SETTINGSPATH) + "ps_panels.json")) #define NAMEINIKEY "NAMES" - +#define DTLSSENDICON ":icons/tx_dtls.png" #define UDPSENDICON ":icons/tx_udp.png" #define TCPSENDICON ":icons/tx_tcp.png" #define UDPRXICON ":icons/rx_udp.png" diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ce702924..7bfd351f 100755 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -74,6 +74,7 @@ MainWindow::MainWindow(QWidget *parent) : if ( ui->udptcpComboBox->currentText().toLower() != "dtls"){ ui->loadKeyButton->hide(); ui->loadCertButton->hide(); + ui->noteServer->hide(); } connect(loadKeyButton, &QPushButton::clicked, this, &MainWindow::on_loadKeyButton_clicked); @@ -2607,7 +2608,12 @@ void MainWindow::on_loadKeyButton_clicked() packetNetwork.keyPath = fileName.toUtf8(); - //QByteArray data = filename; + // Extract the file name from the full path + QFileInfo fileInfo(fileName); + QString fileNameOnly = fileInfo.fileName(); + + // Set the extracted file name as the text on the button + ui->loadKeyButton->setText(fileNameOnly); } @@ -2634,7 +2640,12 @@ void MainWindow::on_loadCertButton_clicked() packetNetwork.certPath = fileName.toUtf8(); - //QByteArray data = filename; + // Extract the file name from the full path + QFileInfo fileInfo(fileName); + QString fileNameOnly = fileInfo.fileName(); + + // Set the extracted file name as the text on the button + ui->loadCertButton->setText(fileNameOnly); } @@ -2657,9 +2668,11 @@ void MainWindow::on_udptcpComboBox_currentIndexChanged(const QString &arg1) if ( ui->udptcpComboBox->currentText().toLower() == "dtls") { ui->loadKeyButton->show(); // Enable when "dtls" is selected ui->loadCertButton->show(); + ui->noteServer->show(); } else { ui->loadKeyButton->hide(); // Disable for other options ui->loadCertButton->hide(); // Disable for other options + ui->noteServer->hide(); } diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 61861f48..0364e7ce 100755 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -247,6 +247,10 @@ DTLS + + + ../../../tx_dtls.png../../../tx_dtls.png + @@ -356,8 +360,22 @@ Load Key File + + + + 210 + 0 + 161 + 21 + + + + *Only if needed by the server + + loadKeyButton loadCertButton + noteServer @@ -556,8 +574,6 @@ - packetSetupGroup - splitter From ea7aa7f5b0d5f0b8b4f9b16cb0954237cdff90a1 Mon Sep 17 00:00:00 2001 From: israel Date: Sun, 22 Oct 2023 17:34:37 +0300 Subject: [PATCH 09/79] load the certs and the key from the settings window, \n get the address and ip from the user, *the opensslPath is too long for CreateProcess --- src/mainwindow.cpp | 4 ++-- src/packetnetwork.cpp | 55 +++++++++++++++++++++++++++++++++++-------- src/packetnetwork.h | 4 ++-- src/settings.cpp | 4 ++-- src/settings.ui | 2 +- 5 files changed, 52 insertions(+), 17 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 7bfd351f..95d7dc44 100755 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2606,7 +2606,7 @@ void MainWindow::on_loadKeyButton_clicked() // } - packetNetwork.keyPath = fileName.toUtf8(); + packetNetwork.keyPath = fileName; // Extract the file name from the full path QFileInfo fileInfo(fileName); @@ -2638,7 +2638,7 @@ void MainWindow::on_loadCertButton_clicked() // } - packetNetwork.certPath = fileName.toUtf8(); + packetNetwork.certPath = fileName; // Extract the file name from the full path QFileInfo fileInfo(fileName); diff --git a/src/packetnetwork.cpp b/src/packetnetwork.cpp index ded5da1f..83fee781 100755 --- a/src/packetnetwork.cpp +++ b/src/packetnetwork.cpp @@ -11,6 +11,7 @@ #include "packetnetwork.h" #include "globals.h" #include "settings.h" +#include #include #include #include @@ -833,27 +834,62 @@ void PacketNetwork::packetToSend(Packet sendpacket) // Replace "your_executable.exe" with the actual path to the executable file //const char* executablePath = "C:/OpenSSL/bin/openssl.exe"; - //const char* arguments = "s_client -dtls1_2 -connect localhost:12345 -key C:/Users/ISRAELS4/Desktop/new openssl certificate/11.9.23-dtls/client-key.pem -cert C:/Users/ISRAELS4/Desktop/new openssl certificate/11.9.23-dtls/client-signed-cert.pem"; + //const char* arguments = "s_client -dtls1_2 -connect sendpacket.TO_IP:12345 -key C:/Users/ISRAELS4/Desktop/new openssl certificate/11.9.23-dtls/client-key.pem -cert C:/Users/ISRAELS4/Desktop/new openssl certificate/11.9.23-dtls/client-signed-cert.pem"; //HINSTANCE hInstance = ShellExecuteA(NULL, "open", executablePath, arguments, NULL, SW_SHOWNORMAL); // Specify the command with arguments - //const char* command = "cmd openssl s_client -dtls1_2 -connect localhost:12345 -key C:/Users/ISRAELS4/Desktop/new openssl certificate/11.9.23-dtls/client-key.pem -cert C:/Users/ISRAELS4/Desktop/new openssl certificate/11.9.23-dtls/client-signed-cert.pem"; + //const char* command = "cmd openssl s_client -dtls1_2 -connect sendpacket.TO_IP:sendpacket.TO_PORT -key C:/Users/ISRAELS4/Desktop/new openssl certificate/11.9.23-dtls/client-key.pem -cert C:/Users/ISRAELS4/Desktop/new openssl certificate/11.9.23-dtls/client-signed-cert.pem"; // Use the system function to run the command //"your_command_here \"C:\\Users\\YourUsername\\YourFile.txt\""; //command && echo input | command + QSettings settings(SETTINGSFILE, QSettings::IniFormat); + //get the pathes for verification from the settings file + QString sslCaPath = settings.value("sslCaPath", "default").toString(); + QString sslLocalCertificatePath = settings.value("sslLocalCertificatePath", "default").toString(); + QString sslPrivateKeyPath = settings.value("sslPrivateKeyPath", "default").toString(); + //get the full path to to ca-signed-cert.pem file + QDir dir(sslCaPath); + if (dir.exists()) { + QStringList nameFilters; + nameFilters << "*.pem"; // Filter for .txt files + + dir.setNameFilters(nameFilters); + QStringList fileList = dir.entryList(); + + if (!fileList.isEmpty()) { + // Select the first file that matches the filter + QString sslCaFullPath = dir.filePath(fileList.first()); + qDebug() << "Selected file: " << sslCaFullPath; + } else { + qDebug() << "No matching files found."; + } + } else { + qDebug() << "Directory does not exist."; + } + //get the data of the packet QByteArray data = sendpacket.getByteArray(); + QString dataStr = QString::fromUtf8(sendpacket.getByteArray()); DWORD status; - - QByteArray opensslPath; + //execute the openssl s_client commands depends if the session is open or close + QString opensslPath; static int isSessionOpen = false; if (!isSessionOpen){ isSessionOpen = true; - opensslPath ="cmd.exe /c (type nul > session.pem) & (echo "+ data + " | openssl s_client -dtls1_2 -connect localhost:12345 -sess_out session.pem -key " + keyPath + " -cert " + certPath +")"; - QString qstr = QString::fromUtf8(opensslPath); - std::wstring wstr = qstr.toStdWString(); + //opensslPath = "cmd.exe /c (type nul > session.pem) & (cd C:/Users/israe/OneDrive - ort braude college of engineering/rsa_encryption) & (echo uvuhvv | openssl s_client -dtls1_2 -connect 127.0.0.1:12345 -sess_out session.pem -key client-key.pem -cert client-signed-cert.pem -CAfile ./ca-signed-cert/signed-cert.pem -verify 2 -cipher AES256-GCM-SHA384)"; + opensslPath ="cmd.exe /c (type nul > session.pem) & (echo "+ dataStr + " | openssl s_client -dtls1_2 -connect " + sendpacket.toIP + ":" + QString::number(sendpacket.port) + " -sess_out session.pem -key " + sslPrivateKeyPath + " -cert " + sslLocalCertificatePath +" -CAfile " + sslCaPath + " -verify 2 -cipher AES256-GCM-SHA384)"; + + //const wchar_t* cmdCommandWide = opensslPath.toStdWString().c_str(); +// int length = opensslPath.length() + 1; +// LPWSTR lpwstr = new WCHAR[length]; +// opensslPath.toWCharArray(lpwstr); +// lpwstr[length - 1] = L'\0'; // Null-terminate the wide string + const wchar_t* wideCmd = opensslPath.toStdWString().c_str(); + std::wstring wstr = opensslPath.toStdWString(); LPWSTR lpwstr = &wstr[0]; + STARTUPINFO si; + si.lpTitle = NULL; PROCESS_INFORMATION pi; ZeroMemory(&si, sizeof(si)); @@ -861,7 +897,7 @@ void PacketNetwork::packetToSend(Packet sendpacket) ZeroMemory(&pi, sizeof(pi)); // Create the process in hidden mode - if (CreateProcess(NULL, lpwstr, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) { + if (CreateProcess(NULL, const_cast(wideCmd), NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) { WaitForSingleObject(pi.hProcess, 10000); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); @@ -877,8 +913,7 @@ void PacketNetwork::packetToSend(Packet sendpacket) } } else{ opensslPath ="cmd.exe /c echo "+ data + " | openssl s_client -dtls1_2 -connect localhost:12345 -sess_in session.pem"; - QString qstr = QString::fromUtf8(opensslPath); - std::wstring wstr = qstr.toStdWString(); + std::wstring wstr = opensslPath.toStdWString(); LPWSTR lpwstr = &wstr[0]; STARTUPINFO si; PROCESS_INFORMATION pi; diff --git a/src/packetnetwork.h b/src/packetnetwork.h index 3ac3b42f..7f0dcdda 100755 --- a/src/packetnetwork.h +++ b/src/packetnetwork.h @@ -37,8 +37,8 @@ class PacketNetwork : public QObject { Q_OBJECT public: - QByteArray keyPath; - QByteArray certPath; + QString keyPath; + QString certPath; explicit PacketNetwork(QObject *parent = nullptr); void init(); diff --git a/src/settings.cpp b/src/settings.cpp index 4f26f573..19dd5b75 100755 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -744,7 +744,7 @@ void Settings::on_sslLocalCertificatePathBrowseButton_clicked() } QString fileName = QFileDialog::getOpenFileName(this, - tr("Choose Cert"), home, tr("Certs (*.pem)")); + tr("Choose Cert"), home, tr("*.*")); if (QFile::exists(fileName)) { ui->sslLocalCertificatePath->setText(fileName); @@ -760,7 +760,7 @@ void Settings::on_sslPrivateKeyPathBrowseButton_clicked() } QString fileName = QFileDialog::getOpenFileName(this, - tr("Choose Key"), home, tr("Keys (*.key, *.pem)")); + tr("Choose Key"), home, tr("*.*")); if (QFile::exists(fileName)) { ui->sslPrivateKeyPath->setText(fileName); diff --git a/src/settings.ui b/src/settings.ui index 6a1fe613..6c66a351 100755 --- a/src/settings.ui +++ b/src/settings.ui @@ -17,7 +17,7 @@ - 3 + 0 From ee4e08096964d6955c7a24912d29b57406a9b17d Mon Sep 17 00:00:00 2001 From: israel Date: Tue, 24 Oct 2023 11:01:21 +0300 Subject: [PATCH 10/79] working with getting all data from the user (from settings and mainwindow windows) --- src/cmd_dtls_client.bat | 15 ++++++++ src/cmd_dtls_client.txt | 15 ++++++++ src/packetnetwork.cpp | 82 +++++++++-------------------------------- 3 files changed, 48 insertions(+), 64 deletions(-) create mode 100644 src/cmd_dtls_client.bat create mode 100644 src/cmd_dtls_client.txt diff --git a/src/cmd_dtls_client.bat b/src/cmd_dtls_client.bat new file mode 100644 index 00000000..9d832d03 --- /dev/null +++ b/src/cmd_dtls_client.bat @@ -0,0 +1,15 @@ +@echo off + +rem Set environment variables for dataStr, toIP, port, sslPrivateKeyPath, sslLocalCertificatePath, and sslCaPath +@echo dataStr=%1 +@echo toIP=%2 +@echo port=%3 +@echo sslPrivateKeyPath=%4 +@echo sslLocalCertificatePath=%5 +@echo sslCaPath=%6 + +rem Create an empty "session.pem" file +type nul > session.pem + +rem Run your long OpenSSL command +echo %dataStr% | openssl s_client -dtls1_2 -connect %toIP%:%port% -sess_out session.pem -key %sslPrivateKeyPath% -cert %sslLocalCertificatePath% -CAfile %sslCaPath% -verify 2 -cipher AES256-GCM-SHA384 diff --git a/src/cmd_dtls_client.txt b/src/cmd_dtls_client.txt new file mode 100644 index 00000000..48fe3730 --- /dev/null +++ b/src/cmd_dtls_client.txt @@ -0,0 +1,15 @@ +@echo off + +rem Set environment variables for dataStr, toIP, port, sslPrivateKeyPath, sslLocalCertificatePath, and sslCaPath +set dataStr=%1 +set toIP=%2 +set port=%3 +set sslPrivateKeyPath=%4 +set sslLocalCertificatePath=%5 +set sslCaPath=%6 + +rem Create an empty "session.pem" file +type nul > session.pem + +rem Run your long OpenSSL command +echo %dataStr% | openssl s_client -dtls1_2 -connect %toIP%:%port% -sess_out session.pem -key %sslPrivateKeyPath% -cert %sslLocalCertificatePath% -CAfile %sslCaPath% -verify 2 -cipher AES256-GCM-SHA384 diff --git a/src/packetnetwork.cpp b/src/packetnetwork.cpp index 83fee781..9da650af 100755 --- a/src/packetnetwork.cpp +++ b/src/packetnetwork.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #ifdef CONSOLE_BUILD class QMessageBox { public: @@ -825,30 +826,14 @@ void PacketNetwork::packetToSend(Packet sendpacket) sendpacket.name = sendpacket.timestamp.toString(DATETIMEFORMAT); if(sendpacket.isDTLS()){ - // QUdpSocket * sendUDP; - // sendUDP = new QUdpSocket(this); - // sendUDP->bind(sendpacket.port); - // QHostAddress resolved = resolveDNS(sendpacket.toIP); - // sendUDP->writeDatagram(sendpacket.getByteArray().append("333333"), resolved, sendpacket.port); - // emit packetSent(sendpacket); - // Replace "your_executable.exe" with the actual path to the executable file - - //const char* executablePath = "C:/OpenSSL/bin/openssl.exe"; - //const char* arguments = "s_client -dtls1_2 -connect sendpacket.TO_IP:12345 -key C:/Users/ISRAELS4/Desktop/new openssl certificate/11.9.23-dtls/client-key.pem -cert C:/Users/ISRAELS4/Desktop/new openssl certificate/11.9.23-dtls/client-signed-cert.pem"; - //HINSTANCE hInstance = ShellExecuteA(NULL, "open", executablePath, arguments, NULL, SW_SHOWNORMAL); - - // Specify the command with arguments - //const char* command = "cmd openssl s_client -dtls1_2 -connect sendpacket.TO_IP:sendpacket.TO_PORT -key C:/Users/ISRAELS4/Desktop/new openssl certificate/11.9.23-dtls/client-key.pem -cert C:/Users/ISRAELS4/Desktop/new openssl certificate/11.9.23-dtls/client-signed-cert.pem"; - - // Use the system function to run the command - //"your_command_here \"C:\\Users\\YourUsername\\YourFile.txt\""; - //command && echo input | command + //open settings file in order to get the ssl valuse of the packet QSettings settings(SETTINGSFILE, QSettings::IniFormat); //get the pathes for verification from the settings file QString sslCaPath = settings.value("sslCaPath", "default").toString(); QString sslLocalCertificatePath = settings.value("sslLocalCertificatePath", "default").toString(); QString sslPrivateKeyPath = settings.value("sslPrivateKeyPath", "default").toString(); //get the full path to to ca-signed-cert.pem file + QString sslCaFullPath; QDir dir(sslCaPath); if (dir.exists()) { QStringList nameFilters; @@ -859,7 +844,7 @@ void PacketNetwork::packetToSend(Packet sendpacket) if (!fileList.isEmpty()) { // Select the first file that matches the filter - QString sslCaFullPath = dir.filePath(fileList.first()); + sslCaFullPath = dir.filePath(fileList.first()); qDebug() << "Selected file: " << sslCaFullPath; } else { qDebug() << "No matching files found."; @@ -870,58 +855,51 @@ void PacketNetwork::packetToSend(Packet sendpacket) //get the data of the packet QByteArray data = sendpacket.getByteArray(); QString dataStr = QString::fromUtf8(sendpacket.getByteArray()); + //status is determine if the connection established or doesn't DWORD status; - //execute the openssl s_client commands depends if the session is open or close + //opensslPath stored the openssl s_client commands depends if the session is open or close QString opensslPath; static int isSessionOpen = false; if (!isSessionOpen){ + //if the session is closed, create session key and save it: isSessionOpen = true; - //opensslPath = "cmd.exe /c (type nul > session.pem) & (cd C:/Users/israe/OneDrive - ort braude college of engineering/rsa_encryption) & (echo uvuhvv | openssl s_client -dtls1_2 -connect 127.0.0.1:12345 -sess_out session.pem -key client-key.pem -cert client-signed-cert.pem -CAfile ./ca-signed-cert/signed-cert.pem -verify 2 -cipher AES256-GCM-SHA384)"; - opensslPath ="cmd.exe /c (type nul > session.pem) & (echo "+ dataStr + " | openssl s_client -dtls1_2 -connect " + sendpacket.toIP + ":" + QString::number(sendpacket.port) + " -sess_out session.pem -key " + sslPrivateKeyPath + " -cert " + sslLocalCertificatePath +" -CAfile " + sslCaPath + " -verify 2 -cipher AES256-GCM-SHA384)"; - - //const wchar_t* cmdCommandWide = opensslPath.toStdWString().c_str(); -// int length = opensslPath.length() + 1; -// LPWSTR lpwstr = new WCHAR[length]; -// opensslPath.toWCharArray(lpwstr); -// lpwstr[length - 1] = L'\0'; // Null-terminate the wide string - const wchar_t* wideCmd = opensslPath.toStdWString().c_str(); + opensslPath ="cmd.exe /c (type nul > session.pem) & (echo "+ dataStr + " | openssl s_client -dtls1_2 -connect " + sendpacket.toIP + ":" + QString::number(sendpacket.port) + " -sess_out session.pem -key \"" + sslPrivateKeyPath + "\" -cert \"" + sslLocalCertificatePath +"\" -CAfile \"" + sslCaFullPath + "\" -verify 2 -cipher AES256-GCM-SHA384)"; + //adjust the opensslPath to be the input for CreateProcess function std::wstring wstr = opensslPath.toStdWString(); + //initiate the proccess's parameters LPWSTR lpwstr = &wstr[0]; - STARTUPINFO si; si.lpTitle = NULL; PROCESS_INFORMATION pi; - ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); ZeroMemory(&pi, sizeof(pi)); - // Create the process in hidden mode - if (CreateProcess(NULL, const_cast(wideCmd), NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) { + if (CreateProcess(NULL, lpwstr, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi)) { WaitForSingleObject(pi.hProcess, 10000); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); } else { // Handle an error if CreateProcess fails - DWORD error = GetLastError(); - printf("CreateProcess failed (%d)\n", GetLastError()); + qDebug() << "CreateProcess failed (%d)\n" + GetLastError(); } + //if the connection doesn't established change modify the session to close session GetExitCodeProcess(pi.hProcess, &status); - if (status!=0){//if the connection doesn't established + if (status!=0){ isSessionOpen = false; } } else{ - opensslPath ="cmd.exe /c echo "+ data + " | openssl s_client -dtls1_2 -connect localhost:12345 -sess_in session.pem"; + //if the session is open, use the session key that has been saved: + opensslPath ="cmd.exe /c echo "+ data + " | openssl s_client -dtls1_2 -connect " + sendpacket.toIP + ":" + QString::number(sendpacket.port)+" -sess_in session.pem"; + //initiate the proccess's parameters std::wstring wstr = opensslPath.toStdWString(); LPWSTR lpwstr = &wstr[0]; STARTUPINFO si; PROCESS_INFORMATION pi; - ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); ZeroMemory(&pi, sizeof(pi)); - // Create the process in hidden mode if (CreateProcess(NULL, lpwstr, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) { WaitForSingleObject(pi.hProcess, 10000); @@ -929,34 +907,10 @@ void PacketNetwork::packetToSend(Packet sendpacket) CloseHandle(pi.hThread); } else { // Handle an error if CreateProcess fails - DWORD error = GetLastError(); - printf("CreateProcess failed (%d)\n", GetLastError()); + qDebug() << "CreateProcess failed (%d)\n" + GetLastError(); } } -// QByteArray opensslPath="cmd.exe /c echo " + data +" |openssl s_client -dtls1_2 -connect localhost:12345 -sess_out session.pem -key " + keyPath + " -cert " + certPath; -// QString qstr = QString::fromUtf8(opensslPath); -// std::wstring wstr = qstr.toStdWString(); -// LPWSTR lpwstr = &wstr[0]; -// STARTUPINFO si; -// PROCESS_INFORMATION pi; - -// ZeroMemory(&si, sizeof(si)); -// si.cb = sizeof(si); -// ZeroMemory(&pi, sizeof(pi)); - -// // Create the process in hidden mode -// if (CreateProcess(NULL, lpwstr, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) { -// WaitForSingleObject(pi.hProcess, 5000); -// CloseHandle(pi.hProcess); -// CloseHandle(pi.hThread); -// } else { -// // Handle an error if CreateProcess fails -// DWORD error = GetLastError(); -// int errnum = GetLastError(); -// printf("CreateProcess failed (%d)\n", GetLastError()); - -// } emit packetSent(sendpacket); } From 6ad22df6b7e242f1d93f0b883f4e0da8bb769237 Mon Sep 17 00:00:00 2001 From: israel Date: Tue, 24 Oct 2023 17:24:19 +0300 Subject: [PATCH 11/79] add the leave session open --- src/packetnetwork.cpp | 91 ++++++++++++++++++++++++++++--------------- src/settings.cpp | 19 ++++++++- src/settings.h | 6 +++ src/settings.ui | 7 ++++ 4 files changed, 90 insertions(+), 33 deletions(-) diff --git a/src/packetnetwork.cpp b/src/packetnetwork.cpp index 9da650af..18fda38e 100755 --- a/src/packetnetwork.cpp +++ b/src/packetnetwork.cpp @@ -828,6 +828,7 @@ void PacketNetwork::packetToSend(Packet sendpacket) if(sendpacket.isDTLS()){ //open settings file in order to get the ssl valuse of the packet QSettings settings(SETTINGSFILE, QSettings::IniFormat); + QStringList allKeys = settings.allKeys(); //get the pathes for verification from the settings file QString sslCaPath = settings.value("sslCaPath", "default").toString(); QString sslLocalCertificatePath = settings.value("sslLocalCertificatePath", "default").toString(); @@ -859,11 +860,65 @@ void PacketNetwork::packetToSend(Packet sendpacket) DWORD status; //opensslPath stored the openssl s_client commands depends if the session is open or close QString opensslPath; - static int isSessionOpen = false; - if (!isSessionOpen){ - //if the session is closed, create session key and save it: - isSessionOpen = true; - opensslPath ="cmd.exe /c (type nul > session.pem) & (echo "+ dataStr + " | openssl s_client -dtls1_2 -connect " + sendpacket.toIP + ":" + QString::number(sendpacket.port) + " -sess_out session.pem -key \"" + sslPrivateKeyPath + "\" -cert \"" + sslLocalCertificatePath +"\" -CAfile \"" + sslCaFullPath + "\" -verify 2 -cipher AES256-GCM-SHA384)"; + QString valueOfLeaveSessOpen = settings.value("leaveSessionOpen").toString(); + //if the user want to leave the session open + if(settings.value("leaveSessionOpen").toString() == "true"){ + static int isSessionOpen = false; + if (!isSessionOpen){ + //if the session is closed, create session key and save it: + isSessionOpen = true; + opensslPath ="cmd.exe /c (type nul > session.pem) & (echo "+ dataStr + " | openssl s_client -dtls1_2 -connect " + sendpacket.toIP + ":" + QString::number(sendpacket.port) + " -sess_out session.pem -key \"" + sslPrivateKeyPath + "\" -cert \"" + sslLocalCertificatePath +"\" -CAfile \"" + sslCaFullPath + "\" -verify 2 -cipher AES256-GCM-SHA384)"; + //adjust the opensslPath to be the input for CreateProcess function + std::wstring wstr = opensslPath.toStdWString(); + //initiate the proccess's parameters + LPWSTR lpwstr = &wstr[0]; + STARTUPINFO si; + si.lpTitle = NULL; + PROCESS_INFORMATION pi; + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + ZeroMemory(&pi, sizeof(pi)); + // Create the process in hidden mode + if (CreateProcess(NULL, lpwstr, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi)) { + WaitForSingleObject(pi.hProcess, 10000); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } else { + // Handle an error if CreateProcess fails + qDebug() << "CreateProcess failed (%d)\n" + GetLastError(); + + } + //if the connection doesn't established change modify the session to close session + GetExitCodeProcess(pi.hProcess, &status); + if (status!=0){ + isSessionOpen = false; + } + } else{ + //if the session is open, use the session key that has been saved: + opensslPath ="cmd.exe /c echo "+ data + " | openssl s_client -dtls1_2 -connect " + sendpacket.toIP + ":" + QString::number(sendpacket.port)+" -sess_in session.pem"; + //initiate the proccess's parameters + std::wstring wstr = opensslPath.toStdWString(); + LPWSTR lpwstr = &wstr[0]; + STARTUPINFO si; + PROCESS_INFORMATION pi; + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + ZeroMemory(&pi, sizeof(pi)); + // Create the process in hidden mode + if (CreateProcess(NULL, lpwstr, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) { + WaitForSingleObject(pi.hProcess, 10000); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } else { + // Handle an error if CreateProcess fails + qDebug() << "CreateProcess failed (%d)\n" + GetLastError(); + + } + } + } + //if the user doesn't want to leave the session open + else{ + opensslPath ="cmd.exe /c (type nul > session.pem) & (echo "+ dataStr + " | openssl s_client -dtls1_2 -connect " + sendpacket.toIP + ":" + QString::number(sendpacket.port) + " -key \"" + sslPrivateKeyPath + "\" -cert \"" + sslLocalCertificatePath +"\" -CAfile \"" + sslCaFullPath + "\" -verify 2 -cipher AES256-GCM-SHA384)"; //adjust the opensslPath to be the input for CreateProcess function std::wstring wstr = opensslPath.toStdWString(); //initiate the proccess's parameters @@ -884,32 +939,6 @@ void PacketNetwork::packetToSend(Packet sendpacket) qDebug() << "CreateProcess failed (%d)\n" + GetLastError(); } - //if the connection doesn't established change modify the session to close session - GetExitCodeProcess(pi.hProcess, &status); - if (status!=0){ - isSessionOpen = false; - } - } else{ - //if the session is open, use the session key that has been saved: - opensslPath ="cmd.exe /c echo "+ data + " | openssl s_client -dtls1_2 -connect " + sendpacket.toIP + ":" + QString::number(sendpacket.port)+" -sess_in session.pem"; - //initiate the proccess's parameters - std::wstring wstr = opensslPath.toStdWString(); - LPWSTR lpwstr = &wstr[0]; - STARTUPINFO si; - PROCESS_INFORMATION pi; - ZeroMemory(&si, sizeof(si)); - si.cb = sizeof(si); - ZeroMemory(&pi, sizeof(pi)); - // Create the process in hidden mode - if (CreateProcess(NULL, lpwstr, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) { - WaitForSingleObject(pi.hProcess, 10000); - CloseHandle(pi.hProcess); - CloseHandle(pi.hThread); - } else { - // Handle an error if CreateProcess fails - qDebug() << "CreateProcess failed (%d)\n" + GetLastError(); - - } } emit packetSent(sendpacket); } diff --git a/src/settings.cpp b/src/settings.cpp index 19dd5b75..3a8f22d3 100755 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #ifndef CONSOLE_BUILD @@ -95,9 +96,10 @@ Settings::Settings(QWidget *parent) : //not working yet... ui->multiSendDelayLabel->hide(); ui->multiSendDelayEdit->hide(); - + //connect(loadKeyButton, &QPushButton::clicked, this, &MainWindow::on_loadKeyButton_clicked); QSettings settings(SETTINGSFILE, QSettings::IniFormat); + settings.setValue("leaveSessionOpen", "false"); QIcon mIcon(":pslogo.png"); setWindowTitle("Packet Sender "+tr("Settings")); @@ -107,7 +109,6 @@ Settings::Settings(QWidget *parent) : ui->displayOrderListTraffic->hide(); ui->displayGroupBoxTraffic->setTitle(""); - loadCredentialTable(); on_genAuthCheck_clicked(false); @@ -174,6 +175,8 @@ Settings::Settings(QWidget *parent) : ui->dateFormatExample->setText(now.toString(dateFormat)); ui->timeFormatExample->setText(now.toString(timeFormat)); + leaveSessionOpen = ui->leaveSessionOpen; + connect(leaveSessionOpen, &QCheckBox::toggled, this, &Settings::on_leaveSessionOpen_StateChanged); connect(ui->dateFormat, &QLineEdit::textChanged, this, [=](QString val) { // use action as you wish @@ -340,6 +343,18 @@ void Settings::statusBarMessage(QString msg) } +void Settings::on_leaveSessionOpen_StateChanged(){ + QSettings settings(SETTINGSFILE, QSettings::IniFormat); + QString leaveSessionOpen = settings.value("leaveSessionOpen", "false").toString(); + if(leaveSessionOpen == "false"){ + settings.setValue("leaveSessionOpen", "true"); + } + else{ + settings.setValue("leaveSessionOpen", "false"); + } + +} + void Settings::on_buttonBox_accepted() { QSettings settings(SETTINGSFILE, QSettings::IniFormat); diff --git a/src/settings.h b/src/settings.h index 5907cf94..cebd9cfe 100644 --- a/src/settings.h +++ b/src/settings.h @@ -9,6 +9,7 @@ #endif #include #include +#include #ifdef CONSOLE_BUILD @@ -67,6 +68,7 @@ class Settings : public QDialog explicit Settings(QWidget *parent = nullptr); ~Settings(); + QCheckBox* leaveSessionOpen; static QStringList defaultPacketTableHeader(); static QStringList defaultTrafficTableHeader(); @@ -100,8 +102,12 @@ class Settings : public QDialog static QString language(); static bool needLanguage(); static QString logHeaderTranslate(QString txt); +public slots: + void on_leaveSessionOpen_StateChanged(); private slots: + + void on_buttonBox_accepted(); void on_asciiResponseEdit_textEdited(const QString &arg1); diff --git a/src/settings.ui b/src/settings.ui index 6c66a351..54fe7a8e 100755 --- a/src/settings.ui +++ b/src/settings.ui @@ -329,6 +329,13 @@ + + + + Leave the session open + + + From 2e0e47f37d4800e524edd98e67d516e219f06061 Mon Sep 17 00:00:00 2001 From: israel Date: Wed, 25 Oct 2023 16:54:26 +0300 Subject: [PATCH 12/79] Leave session option is working, not for all cases --- src/main.cpp | 2 ++ src/mainwindow.cpp | 18 ++++++++++++++++-- src/mainwindow.h | 4 ++-- src/packetnetwork.cpp | 11 ++++++----- src/settings.cpp | 18 ++++-------------- src/settings.h | 8 +++++--- 6 files changed, 35 insertions(+), 26 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 8669bd03..1717a2c4 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -95,6 +95,8 @@ void myMessageOutputDisable(QtMsgType type, const QMessageLogContext &context, c int main(int argc, char *argv[]) { + QSettings settings(SETTINGSFILE, QSettings::IniFormat); + settings.setValue("leaveSessionOpen", "false"); int debugMode = DEBUGMODE; if (QFile::exists("DEBUGMODE")) { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 95d7dc44..fde3b349 100755 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -58,6 +58,7 @@ #include "postdatagen.h" #include "panelgenerator.h" +int MainWindow::isSessionOpen = false; int hexToInt(QChar hex); void parserMajorMinorBuild(QString sw, unsigned int &major, unsigned int &minor, unsigned int &build); extern void themeTheButton(QPushButton * button); @@ -82,7 +83,7 @@ MainWindow::MainWindow(QWidget *parent) : QSettings settings(SETTINGSFILE, QSettings::IniFormat); - + //settings.setValue("leaveSessionOpen", "false"); QIcon mIcon(":pslogo.png"); @@ -2246,9 +2247,22 @@ void MainWindow::on_actionHelp_triggered() QDesktopServices::openUrl(QUrl("https://packetsender.com/documentation")); } +void MainWindow::on_leaveSessionOpen_StateChanged(){ + //ui.checkBox->setChecked(checkBoxState); + + QSettings settings(SETTINGSFILE, QSettings::IniFormat); + QString leaveSessionOpen = settings.value("leaveSessionOpen", "false").toString(); + if(leaveSessionOpen == "false"){ + settings.setValue("leaveSessionOpen", "true"); + } + else{ + settings.setValue("leaveSessionOpen", "false"); + } +} + void MainWindow::on_actionSettings_triggered() { - Settings settings; + Settings settings(this); int accepted = settings.exec(); if (accepted) { setIPMode(); diff --git a/src/mainwindow.h b/src/mainwindow.h index c640c397..064fb12c 100755 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -69,6 +69,7 @@ class MainWindow : public QMainWindow Q_OBJECT public: + static int isSessionOpen; explicit MainWindow(QWidget *parent = nullptr); ~MainWindow(); @@ -95,11 +96,10 @@ class MainWindow : public QMainWindow void sendPacket(Packet sendpacket); public slots: + void on_leaveSessionOpen_StateChanged(); void toTrafficLog(Packet logPacket); void cancelResends(); void applyNetworkSettings(); - - void toggleUDPServer(); void toggleTCPServer(); void toggleSSLServer(); diff --git a/src/packetnetwork.cpp b/src/packetnetwork.cpp index 18fda38e..0bc5e446 100755 --- a/src/packetnetwork.cpp +++ b/src/packetnetwork.cpp @@ -863,10 +863,10 @@ void PacketNetwork::packetToSend(Packet sendpacket) QString valueOfLeaveSessOpen = settings.value("leaveSessionOpen").toString(); //if the user want to leave the session open if(settings.value("leaveSessionOpen").toString() == "true"){ - static int isSessionOpen = false; - if (!isSessionOpen){ + + if (!MainWindow::isSessionOpen){ //if the session is closed, create session key and save it: - isSessionOpen = true; + MainWindow::isSessionOpen = true; opensslPath ="cmd.exe /c (type nul > session.pem) & (echo "+ dataStr + " | openssl s_client -dtls1_2 -connect " + sendpacket.toIP + ":" + QString::number(sendpacket.port) + " -sess_out session.pem -key \"" + sslPrivateKeyPath + "\" -cert \"" + sslLocalCertificatePath +"\" -CAfile \"" + sslCaFullPath + "\" -verify 2 -cipher AES256-GCM-SHA384)"; //adjust the opensslPath to be the input for CreateProcess function std::wstring wstr = opensslPath.toStdWString(); @@ -885,13 +885,14 @@ void PacketNetwork::packetToSend(Packet sendpacket) CloseHandle(pi.hThread); } else { // Handle an error if CreateProcess fails - qDebug() << "CreateProcess failed (%d)\n" + GetLastError(); + //DWORD errorCode=GetLastError(); + //qDebug() << "CreateProcess failed (%d)\n" + GetLastError(); } //if the connection doesn't established change modify the session to close session GetExitCodeProcess(pi.hProcess, &status); if (status!=0){ - isSessionOpen = false; + MainWindow::isSessionOpen = false; } } else{ //if the session is open, use the session key that has been saved: diff --git a/src/settings.cpp b/src/settings.cpp index 3a8f22d3..8b5ee505 100755 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -85,8 +85,9 @@ const QString Settings::HTTPHEADERINDEX = "HTTPHeader:"; #ifndef CONSOLE_BUILD -Settings::Settings(QWidget *parent) : +Settings::Settings(QWidget *parent, MainWindow* mw) : QDialog(parent), + rmw(mw), ui(new Ui::Settings) { ui->setupUi(this); @@ -99,7 +100,7 @@ Settings::Settings(QWidget *parent) : //connect(loadKeyButton, &QPushButton::clicked, this, &MainWindow::on_loadKeyButton_clicked); QSettings settings(SETTINGSFILE, QSettings::IniFormat); - settings.setValue("leaveSessionOpen", "false"); + //settings.setValue("leaveSessionOpen", "false"); QIcon mIcon(":pslogo.png"); setWindowTitle("Packet Sender "+tr("Settings")); @@ -176,7 +177,7 @@ Settings::Settings(QWidget *parent) : ui->timeFormatExample->setText(now.toString(timeFormat)); leaveSessionOpen = ui->leaveSessionOpen; - connect(leaveSessionOpen, &QCheckBox::toggled, this, &Settings::on_leaveSessionOpen_StateChanged); + connect(leaveSessionOpen, &QCheckBox::toggled, dynamic_cast(parent), &MainWindow::on_leaveSessionOpen_StateChanged); connect(ui->dateFormat, &QLineEdit::textChanged, this, [=](QString val) { // use action as you wish @@ -343,17 +344,6 @@ void Settings::statusBarMessage(QString msg) } -void Settings::on_leaveSessionOpen_StateChanged(){ - QSettings settings(SETTINGSFILE, QSettings::IniFormat); - QString leaveSessionOpen = settings.value("leaveSessionOpen", "false").toString(); - if(leaveSessionOpen == "false"){ - settings.setValue("leaveSessionOpen", "true"); - } - else{ - settings.setValue("leaveSessionOpen", "false"); - } - -} void Settings::on_buttonBox_accepted() { diff --git a/src/settings.h b/src/settings.h index cebd9cfe..0728637c 100644 --- a/src/settings.h +++ b/src/settings.h @@ -2,6 +2,7 @@ #define SETTINGS_H #include "globals.h" +#include "mainwindow.h" #ifndef CONSOLE_BUILD #include @@ -65,7 +66,9 @@ class Settings : public QDialog Q_OBJECT public: - explicit Settings(QWidget *parent = nullptr); + MainWindow* rmw; + + explicit Settings(QWidget *parent = nullptr, MainWindow* mw = nullptr); ~Settings(); QCheckBox* leaveSessionOpen; @@ -102,8 +105,6 @@ class Settings : public QDialog static QString language(); static bool needLanguage(); static QString logHeaderTranslate(QString txt); -public slots: - void on_leaveSessionOpen_StateChanged(); private slots: @@ -142,6 +143,7 @@ private slots: void on_chooseLanguageButton_clicked(); private: + QWidget *parentWidget; Ui::Settings *ui; QList packetsSaved; QStringList packetTableHeaders; From 68bb7b7a96526ea23dda272eec52340054508cf0 Mon Sep 17 00:00:00 2001 From: israel Date: Wed, 25 Oct 2023 17:36:02 +0300 Subject: [PATCH 13/79] the leave session open ui checkbox is saved even if settings window closed --- src/settings.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/settings.cpp b/src/settings.cpp index 8b5ee505..3da72842 100755 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -92,6 +92,7 @@ Settings::Settings(QWidget *parent, MainWindow* mw) : { ui->setupUi(this); + setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint); //not working yet... @@ -100,7 +101,11 @@ Settings::Settings(QWidget *parent, MainWindow* mw) : //connect(loadKeyButton, &QPushButton::clicked, this, &MainWindow::on_loadKeyButton_clicked); QSettings settings(SETTINGSFILE, QSettings::IniFormat); - //settings.setValue("leaveSessionOpen", "false"); + if(settings.value("leaveSessionOpen").toString() == "false"){ + ui->leaveSessionOpen->setChecked(false); + } else { + ui->leaveSessionOpen->setChecked(true); + } QIcon mIcon(":pslogo.png"); setWindowTitle("Packet Sender "+tr("Settings")); From 0b1d91fb9de261a8a5c0cfbec80a13412e81fc74 Mon Sep 17 00:00:00 2001 From: israel Date: Thu, 26 Oct 2023 10:48:15 +0300 Subject: [PATCH 14/79] leave_session_open is working, and have been tested --- src/packetnetwork.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/packetnetwork.cpp b/src/packetnetwork.cpp index 0bc5e446..1a99558d 100755 --- a/src/packetnetwork.cpp +++ b/src/packetnetwork.cpp @@ -919,7 +919,10 @@ void PacketNetwork::packetToSend(Packet sendpacket) } //if the user doesn't want to leave the session open else{ - opensslPath ="cmd.exe /c (type nul > session.pem) & (echo "+ dataStr + " | openssl s_client -dtls1_2 -connect " + sendpacket.toIP + ":" + QString::number(sendpacket.port) + " -key \"" + sslPrivateKeyPath + "\" -cert \"" + sslLocalCertificatePath +"\" -CAfile \"" + sslCaFullPath + "\" -verify 2 -cipher AES256-GCM-SHA384)"; + MainWindow::isSessionOpen = false; + opensslPath ="cmd.exe /c (del session.pem) & (echo "+ dataStr + " | openssl s_client -dtls1_2 -connect " + sendpacket.toIP + ":" + QString::number(sendpacket.port) + " -key \"" + sslPrivateKeyPath + "\" -cert \"" + sslLocalCertificatePath +"\" -CAfile \"" + sslCaFullPath + "\" -verify 2 -cipher AES256-GCM-SHA384)"; + //opensslPath = "cmd.exe /c where filename.ext"; + //adjust the opensslPath to be the input for CreateProcess function std::wstring wstr = opensslPath.toStdWString(); //initiate the proccess's parameters From c09e16fcab22ee6b840efa5575322a94687a769d Mon Sep 17 00:00:00 2001 From: israel Date: Thu, 26 Oct 2023 15:02:00 +0300 Subject: [PATCH 15/79] added execCmd --- src/packetnetwork.cpp | 130 ++++++++++++++++-------------------------- src/packetnetwork.h | 5 ++ 2 files changed, 55 insertions(+), 80 deletions(-) diff --git a/src/packetnetwork.cpp b/src/packetnetwork.cpp index 1a99558d..d5d67b29 100755 --- a/src/packetnetwork.cpp +++ b/src/packetnetwork.cpp @@ -826,15 +826,20 @@ void PacketNetwork::packetToSend(Packet sendpacket) sendpacket.name = sendpacket.timestamp.toString(DATETIMEFORMAT); if(sendpacket.isDTLS()){ + //the array of cmdComponents: dataStr, toIp, toPort, sslPrivateKeyPath, sslLocalCertificatePath, sslCaFullPath + QString cmdComponents[6]; + //get the data of the packet + cmdComponents[0] = QString::fromUtf8(sendpacket.getByteArray()); + cmdComponents[1] = sendpacket.toIP; + cmdComponents[2] = QString::number(sendpacket.port); //open settings file in order to get the ssl valuse of the packet QSettings settings(SETTINGSFILE, QSettings::IniFormat); - QStringList allKeys = settings.allKeys(); - //get the pathes for verification from the settings file + //get the pathes for verification from the settings + cmdComponents[3] = settings.value("sslPrivateKeyPath", "default").toString(); + cmdComponents[4] = settings.value("sslLocalCertificatePath", "default").toString(); QString sslCaPath = settings.value("sslCaPath", "default").toString(); - QString sslLocalCertificatePath = settings.value("sslLocalCertificatePath", "default").toString(); - QString sslPrivateKeyPath = settings.value("sslPrivateKeyPath", "default").toString(); + //get the full path to to ca-signed-cert.pem file - QString sslCaFullPath; QDir dir(sslCaPath); if (dir.exists()) { QStringList nameFilters; @@ -845,104 +850,41 @@ void PacketNetwork::packetToSend(Packet sendpacket) if (!fileList.isEmpty()) { // Select the first file that matches the filter - sslCaFullPath = dir.filePath(fileList.first()); - qDebug() << "Selected file: " << sslCaFullPath; + cmdComponents[5] = dir.filePath(fileList.first()); } else { qDebug() << "No matching files found."; } } else { qDebug() << "Directory does not exist."; } - //get the data of the packet - QByteArray data = sendpacket.getByteArray(); - QString dataStr = QString::fromUtf8(sendpacket.getByteArray()); + //status is determine if the connection established or doesn't - DWORD status; + DWORD status = 0; + DWORD& statusRef = status; //opensslPath stored the openssl s_client commands depends if the session is open or close QString opensslPath; - QString valueOfLeaveSessOpen = settings.value("leaveSessionOpen").toString(); //if the user want to leave the session open if(settings.value("leaveSessionOpen").toString() == "true"){ if (!MainWindow::isSessionOpen){ //if the session is closed, create session key and save it: MainWindow::isSessionOpen = true; - opensslPath ="cmd.exe /c (type nul > session.pem) & (echo "+ dataStr + " | openssl s_client -dtls1_2 -connect " + sendpacket.toIP + ":" + QString::number(sendpacket.port) + " -sess_out session.pem -key \"" + sslPrivateKeyPath + "\" -cert \"" + sslLocalCertificatePath +"\" -CAfile \"" + sslCaFullPath + "\" -verify 2 -cipher AES256-GCM-SHA384)"; - //adjust the opensslPath to be the input for CreateProcess function - std::wstring wstr = opensslPath.toStdWString(); - //initiate the proccess's parameters - LPWSTR lpwstr = &wstr[0]; - STARTUPINFO si; - si.lpTitle = NULL; - PROCESS_INFORMATION pi; - ZeroMemory(&si, sizeof(si)); - si.cb = sizeof(si); - ZeroMemory(&pi, sizeof(pi)); - // Create the process in hidden mode - if (CreateProcess(NULL, lpwstr, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi)) { - WaitForSingleObject(pi.hProcess, 10000); - CloseHandle(pi.hProcess); - CloseHandle(pi.hThread); - } else { - // Handle an error if CreateProcess fails - //DWORD errorCode=GetLastError(); - //qDebug() << "CreateProcess failed (%d)\n" + GetLastError(); + opensslPath ="cmd.exe /c (type nul > session.pem) & (echo "+ cmdComponents[0] + " | openssl s_client -dtls1_2 -connect " + cmdComponents[1] + ":" + cmdComponents[2] + " -sess_out session.pem -key \"" + cmdComponents[3] + "\" -cert \"" + cmdComponents[4] +"\" -CAfile \"" + cmdComponents[5] + "\" -verify 2 -cipher AES256-GCM-SHA384)"; + execCmd(opensslPath, statusRef); + - } - //if the connection doesn't established change modify the session to close session - GetExitCodeProcess(pi.hProcess, &status); - if (status!=0){ - MainWindow::isSessionOpen = false; - } } else{ //if the session is open, use the session key that has been saved: - opensslPath ="cmd.exe /c echo "+ data + " | openssl s_client -dtls1_2 -connect " + sendpacket.toIP + ":" + QString::number(sendpacket.port)+" -sess_in session.pem"; - //initiate the proccess's parameters - std::wstring wstr = opensslPath.toStdWString(); - LPWSTR lpwstr = &wstr[0]; - STARTUPINFO si; - PROCESS_INFORMATION pi; - ZeroMemory(&si, sizeof(si)); - si.cb = sizeof(si); - ZeroMemory(&pi, sizeof(pi)); - // Create the process in hidden mode - if (CreateProcess(NULL, lpwstr, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) { - WaitForSingleObject(pi.hProcess, 10000); - CloseHandle(pi.hProcess); - CloseHandle(pi.hThread); - } else { - // Handle an error if CreateProcess fails - qDebug() << "CreateProcess failed (%d)\n" + GetLastError(); - - } + //opensslPath ="cmd.exe /c echo "+ data + " | openssl s_client -dtls1_2 -connect " + sendpacket.toIP + ":" + QString::number(sendpacket.port)+" -sess_in session.pem"; + opensslPath ="cmd.exe /c echo "+ cmdComponents[0] + " | openssl s_client -dtls1_2 -connect " + cmdComponents[1] + ":" + cmdComponents[2] +" -sess_in session.pem"; + execCmd(opensslPath, statusRef); } } //if the user doesn't want to leave the session open else{ MainWindow::isSessionOpen = false; - opensslPath ="cmd.exe /c (del session.pem) & (echo "+ dataStr + " | openssl s_client -dtls1_2 -connect " + sendpacket.toIP + ":" + QString::number(sendpacket.port) + " -key \"" + sslPrivateKeyPath + "\" -cert \"" + sslLocalCertificatePath +"\" -CAfile \"" + sslCaFullPath + "\" -verify 2 -cipher AES256-GCM-SHA384)"; - //opensslPath = "cmd.exe /c where filename.ext"; - - //adjust the opensslPath to be the input for CreateProcess function - std::wstring wstr = opensslPath.toStdWString(); - //initiate the proccess's parameters - LPWSTR lpwstr = &wstr[0]; - STARTUPINFO si; - si.lpTitle = NULL; - PROCESS_INFORMATION pi; - ZeroMemory(&si, sizeof(si)); - si.cb = sizeof(si); - ZeroMemory(&pi, sizeof(pi)); - // Create the process in hidden mode - if (CreateProcess(NULL, lpwstr, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi)) { - WaitForSingleObject(pi.hProcess, 10000); - CloseHandle(pi.hProcess); - CloseHandle(pi.hThread); - } else { - // Handle an error if CreateProcess fails - qDebug() << "CreateProcess failed (%d)\n" + GetLastError(); - - } + opensslPath ="cmd.exe /c (del session.pem) & (echo "+ cmdComponents[0] + " | openssl s_client -dtls1_2 -connect " + cmdComponents[1] + ":" + cmdComponents[2] + " -key \"" + cmdComponents[3] + "\" -cert \"" + cmdComponents[4] +"\" -CAfile \"" + cmdComponents[5] + "\" -verify 2 -cipher AES256-GCM-SHA384)"; + execCmd(opensslPath, statusRef); } emit packetSent(sendpacket); } @@ -1065,8 +1007,36 @@ void PacketNetwork::packetToSend(Packet sendpacket) } +} +//isDTLS function +void PacketNetwork::execCmd(QString opensslPath, DWORD& statusRef){ + //adjust the opensslPath to be the input for CreateProcess function + std::wstring wstr = opensslPath.toStdWString(); + //initiate the proccess's parameters + LPWSTR lpwstr = &wstr[0]; + STARTUPINFO si; + si.lpTitle = NULL; + PROCESS_INFORMATION pi; + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + ZeroMemory(&pi, sizeof(pi)); + // Create the process in hidden mode + if (CreateProcess(NULL, lpwstr, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) { + WaitForSingleObject(pi.hProcess, 10000); + //if the connection doesn't established change modify the session to close session + GetExitCodeProcess(pi.hProcess, &statusRef); + if (statusRef!=0){ + MainWindow::isSessionOpen = false; + } + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } else { + // Handle an error if CreateProcess fails + //DWORD errorCode=GetLastError(); + //qDebug() << "CreateProcess failed (%d)\n" + GetLastError(); + } } void PacketNetwork::httpError(QNetworkRequest* pReply) diff --git a/src/packetnetwork.h b/src/packetnetwork.h index 7f0dcdda..58a55510 100755 --- a/src/packetnetwork.h +++ b/src/packetnetwork.h @@ -30,6 +30,8 @@ #include "persistentconnection.h" #endif #include +#include + @@ -42,6 +44,9 @@ class PacketNetwork : public QObject explicit PacketNetwork(QObject *parent = nullptr); void init(); + //isDTLS function + void execCmd(QString opensslPath, DWORD& statusRef); + QString debugQByteArray(QByteArray debugArray); QString getUDPPortString(); From d69123fe8b591bd7514689ac4ffa450d74e58146 Mon Sep 17 00:00:00 2001 From: israel Date: Sun, 29 Oct 2023 17:06:18 +0200 Subject: [PATCH 16/79] function of isDTLS were extracted, traffic log presents errors and ciphers combobox was added to ps --- src/mainwindow.cpp | 18 +++++++ src/mainwindow.h | 4 ++ src/mainwindow.ui | 122 +++++++++++++++++++++++++++++++++++++++++- src/packetnetwork.cpp | 96 ++++++++++++++++++--------------- src/packetnetwork.h | 5 +- src/settings.cpp | 1 - src/tx_dtls.png | Bin 0 -> 45053 bytes 7 files changed, 199 insertions(+), 47 deletions(-) create mode 100644 src/tx_dtls.png diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index fde3b349..f484c658 100755 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -72,12 +72,16 @@ MainWindow::MainWindow(QWidget *parent) : ui->setupUi(this); + cipherCb = ui->cipherCb; if ( ui->udptcpComboBox->currentText().toLower() != "dtls"){ ui->loadKeyButton->hide(); ui->loadCertButton->hide(); ui->noteServer->hide(); + cipherCb->hide(); + ui->CipherLable->hide(); } + connect(cipherCb, &QComboBox::editTextChanged, this, &MainWindow::on_cipherCb_currentIndexChanged); connect(loadKeyButton, &QPushButton::clicked, this, &MainWindow::on_loadKeyButton_clicked); connect(loadCertButton, &QPushButton::clicked, this, &MainWindow::on_loadCertButton_clicked); @@ -2683,10 +2687,15 @@ void MainWindow::on_udptcpComboBox_currentIndexChanged(const QString &arg1) ui->loadKeyButton->show(); // Enable when "dtls" is selected ui->loadCertButton->show(); ui->noteServer->show(); + cipherCb->show(); + ui->CipherLable->show(); } else { ui->loadKeyButton->hide(); // Disable for other options ui->loadCertButton->hide(); // Disable for other options ui->noteServer->hide(); + cipherCb->hide(); + ui->CipherLable->hide(); + } @@ -2721,6 +2730,15 @@ void MainWindow::on_udptcpComboBox_currentIndexChanged(const QString &arg1) ui->genPostDataButton->setVisible(isPost); } +void MainWindow::on_cipherCb_currentIndexChanged(){ + QSettings settings(SETTINGSFILE, QSettings::IniFormat); + settings.setValue("cipher", cipherCb->currentText()); + //create new session even if the leave open session checkbox is pushed create new session, because the cipher has been changed + isSessionOpen = false; + +} + + void MainWindow::on_genPostDataButton_clicked() { PostDataGen * phttp = new PostDataGen(this, ui->packetASCIIEdit->text()); diff --git a/src/mainwindow.h b/src/mainwindow.h index 064fb12c..e371072f 100755 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -10,6 +10,7 @@ #ifndef MAINWINDOW_H #define MAINWINDOW_H +#include #include #include #include @@ -78,6 +79,7 @@ class MainWindow : public QMainWindow void loadPacketsTable(); + QComboBox* cipherCb; QPushButton *loadKeyButton; QPushButton *loadCertButton; QPushButton *generatePSLink(); @@ -220,6 +222,8 @@ class MainWindow : public QMainWindow void on_udptcpComboBox_currentIndexChanged(const QString &arg1); + void on_cipherCb_currentIndexChanged(); + void on_requestPathEdit_editingFinished(); void on_genPostDataButton_clicked(); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 0364e7ce..a6c60260 100755 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -249,7 +249,7 @@ - ../../../tx_dtls.png../../../tx_dtls.png + ../../../עבודה/dtls_logo/tx_dtls.png../../../עבודה/dtls_logo/tx_dtls.png @@ -373,9 +373,129 @@ *Only if needed by the server + + + + 650 + 0 + 191 + 24 + + + + + AES256-GCM-SHA384 + + + + + AES128-GCM-SHA256 + + + + + AES256-GCM-SHA384 + + + + + AES128-GCM-SHA256 + + + + + AES128-SHA256 + + + + + AES256-SHA384 + + + + + AES128-SHA + + + + + AES256-SHA + + + + + CHACHA20-POLY1305-SHA256 + + + + + RC4-MD5 + + + + + RC4-SHA + + + + + CAMELLIA128-SHA256 + + + + + CAMELLIA256-SHA + + + + + ECDHE-RSA-AES128-GCM-SHA256 + + + + + ECDHE-RSA-AES256-GCM-SHA384 + + + + + ECDHE-ECDSA-AES128-GCM-SHA256 + + + + + ECDHE-ECDSA-AES256-GCM-SHA384 + + + + + DHE-RSA-AES128-GCM-SHA256 + + + + + DHE-RSA-AES256-GCM-SHA384 + + + + + + + 560 + 0 + 81 + 20 + + + + Cipher Suites: + + loadKeyButton loadCertButton noteServer + cipherCb + CipherLable diff --git a/src/packetnetwork.cpp b/src/packetnetwork.cpp index d5d67b29..7f8f8a5e 100755 --- a/src/packetnetwork.cpp +++ b/src/packetnetwork.cpp @@ -826,38 +826,13 @@ void PacketNetwork::packetToSend(Packet sendpacket) sendpacket.name = sendpacket.timestamp.toString(DATETIMEFORMAT); if(sendpacket.isDTLS()){ - //the array of cmdComponents: dataStr, toIp, toPort, sslPrivateKeyPath, sslLocalCertificatePath, sslCaFullPath - QString cmdComponents[6]; - //get the data of the packet - cmdComponents[0] = QString::fromUtf8(sendpacket.getByteArray()); - cmdComponents[1] = sendpacket.toIP; - cmdComponents[2] = QString::number(sendpacket.port); //open settings file in order to get the ssl valuse of the packet QSettings settings(SETTINGSFILE, QSettings::IniFormat); - //get the pathes for verification from the settings - cmdComponents[3] = settings.value("sslPrivateKeyPath", "default").toString(); - cmdComponents[4] = settings.value("sslLocalCertificatePath", "default").toString(); - QString sslCaPath = settings.value("sslCaPath", "default").toString(); - - //get the full path to to ca-signed-cert.pem file - QDir dir(sslCaPath); - if (dir.exists()) { - QStringList nameFilters; - nameFilters << "*.pem"; // Filter for .txt files - - dir.setNameFilters(nameFilters); - QStringList fileList = dir.entryList(); - - if (!fileList.isEmpty()) { - // Select the first file that matches the filter - cmdComponents[5] = dir.filePath(fileList.first()); - } else { - qDebug() << "No matching files found."; - } - } else { - qDebug() << "Directory does not exist."; + if (settings.status() != QSettings::NoError) { + sendpacket.errorString ="Can't open settings file."; } - + //the vector of cmdComponents contains: dataStr, toIp, toPort, sslPrivateKeyPath, sslLocalCertificatePath, sslCaFullPath + std::vector cmdComponents = getCmdInput(sendpacket, settings); //status is determine if the connection established or doesn't DWORD status = 0; DWORD& statusRef = status; @@ -865,26 +840,23 @@ void PacketNetwork::packetToSend(Packet sendpacket) QString opensslPath; //if the user want to leave the session open if(settings.value("leaveSessionOpen").toString() == "true"){ - + //if the session is closed, create session key and save it: if (!MainWindow::isSessionOpen){ - //if the session is closed, create session key and save it: MainWindow::isSessionOpen = true; - opensslPath ="cmd.exe /c (type nul > session.pem) & (echo "+ cmdComponents[0] + " | openssl s_client -dtls1_2 -connect " + cmdComponents[1] + ":" + cmdComponents[2] + " -sess_out session.pem -key \"" + cmdComponents[3] + "\" -cert \"" + cmdComponents[4] +"\" -CAfile \"" + cmdComponents[5] + "\" -verify 2 -cipher AES256-GCM-SHA384)"; - execCmd(opensslPath, statusRef); - - + opensslPath ="cmd.exe /c (type nul > session.pem) & (echo "+ cmdComponents[0] + " | openssl s_client -dtls1_2 -connect " + cmdComponents[1] + ":" + cmdComponents[2] + " -sess_out session.pem -key \"" + cmdComponents[3] + "\" -cert \"" + cmdComponents[4] +"\" -CAfile \"" + cmdComponents[5] + "\" -verify 2 -cipher " + cmdComponents[6] +")"; + execCmd(opensslPath, statusRef, sendpacket); + //if the session is open, use the session key that has been saved: } else{ - //if the session is open, use the session key that has been saved: //opensslPath ="cmd.exe /c echo "+ data + " | openssl s_client -dtls1_2 -connect " + sendpacket.toIP + ":" + QString::number(sendpacket.port)+" -sess_in session.pem"; opensslPath ="cmd.exe /c echo "+ cmdComponents[0] + " | openssl s_client -dtls1_2 -connect " + cmdComponents[1] + ":" + cmdComponents[2] +" -sess_in session.pem"; - execCmd(opensslPath, statusRef); + execCmd(opensslPath, statusRef, sendpacket); } } //if the user doesn't want to leave the session open else{ MainWindow::isSessionOpen = false; - opensslPath ="cmd.exe /c (del session.pem) & (echo "+ cmdComponents[0] + " | openssl s_client -dtls1_2 -connect " + cmdComponents[1] + ":" + cmdComponents[2] + " -key \"" + cmdComponents[3] + "\" -cert \"" + cmdComponents[4] +"\" -CAfile \"" + cmdComponents[5] + "\" -verify 2 -cipher AES256-GCM-SHA384)"; - execCmd(opensslPath, statusRef); + opensslPath ="cmd.exe /c (del session.pem) & (echo "+ cmdComponents[0] + " | openssl s_client -dtls1_2 -connect " + cmdComponents[1] + ":" + cmdComponents[2] + " -key \"" + cmdComponents[3] + "\" -cert \"" + cmdComponents[4] +"\" -CAfile \"" + cmdComponents[5] + "\" -verify 2 -cipher " + cmdComponents[6] +")"; + execCmd(opensslPath, statusRef, sendpacket); } emit packetSent(sendpacket); } @@ -1011,7 +983,7 @@ void PacketNetwork::packetToSend(Packet sendpacket) //isDTLS function -void PacketNetwork::execCmd(QString opensslPath, DWORD& statusRef){ +void PacketNetwork::execCmd(QString opensslPath, DWORD& statusRef, Packet& sendpacket){ //adjust the opensslPath to be the input for CreateProcess function std::wstring wstr = opensslPath.toStdWString(); //initiate the proccess's parameters @@ -1027,18 +999,56 @@ void PacketNetwork::execCmd(QString opensslPath, DWORD& statusRef){ WaitForSingleObject(pi.hProcess, 10000); //if the connection doesn't established change modify the session to close session GetExitCodeProcess(pi.hProcess, &statusRef); + //connection error if (statusRef!=0){ MainWindow::isSessionOpen = false; + sendpacket.errorString = "Connection error, openssl error code is: " + QString::number(static_cast(statusRef)); } CloseHandle(pi.hProcess); CloseHandle(pi.hThread); } else { - // Handle an error if CreateProcess fails - //DWORD errorCode=GetLastError(); - //qDebug() << "CreateProcess failed (%d)\n" + GetLastError(); + // error with the process creation + sendpacket.errorString = "process creation was faild"; } } +std::vector PacketNetwork::getCmdInput(Packet sendpacket, QSettings& settings){ + //the array of cmdComponents: dataStr, toIp, toPort, sslPrivateKeyPath, sslLocalCertificatePath, sslCaFullPath + std::vector cmdComponents; + + //get the data of the packet + cmdComponents.push_back(QString::fromUtf8(sendpacket.getByteArray())); + cmdComponents.push_back(sendpacket.toIP); + cmdComponents.push_back(QString::number(sendpacket.port)); + + //get the pathes for verification from the settings + cmdComponents.push_back(settings.value("sslPrivateKeyPath", "default").toString()); + cmdComponents.push_back(settings.value("sslLocalCertificatePath", "default").toString()); + QString sslCaPath = settings.value("sslCaPath", "default").toString(); + + //get the full path to to ca-signed-cert.pem file + QDir dir(sslCaPath); + if (dir.exists()) { + QStringList nameFilters; + nameFilters << "*.pem"; // Filter for .txt files + + dir.setNameFilters(nameFilters); + QStringList fileList = dir.entryList(); + + if (!fileList.isEmpty()) { + // Select the first file that matches the filter + cmdComponents.push_back(dir.filePath(fileList.first())); + } else { + qDebug() << "No matching files found."; + } + } else { + qDebug() << "Directory does not exist."; + } + cmdComponents.push_back(settings.value("cipher", "AES256-GCM-SHA384").toString()); + return cmdComponents; +} + + void PacketNetwork::httpError(QNetworkRequest* pReply) { QDEBUGVAR(pReply); diff --git a/src/packetnetwork.h b/src/packetnetwork.h index 58a55510..f40aebe8 100755 --- a/src/packetnetwork.h +++ b/src/packetnetwork.h @@ -31,7 +31,7 @@ #endif #include #include - +#include @@ -45,7 +45,8 @@ class PacketNetwork : public QObject void init(); //isDTLS function - void execCmd(QString opensslPath, DWORD& statusRef); + void execCmd(QString opensslPath, DWORD& statusRef, Packet& sendpacket); + std::vector getCmdInput(Packet sendpacket, QSettings &settings); QString debugQByteArray(QByteArray debugArray); diff --git a/src/settings.cpp b/src/settings.cpp index 3da72842..37cfdeb1 100755 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -92,7 +92,6 @@ Settings::Settings(QWidget *parent, MainWindow* mw) : { ui->setupUi(this); - setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint); //not working yet... diff --git a/src/tx_dtls.png b/src/tx_dtls.png new file mode 100644 index 0000000000000000000000000000000000000000..e895137ecd9d1b8e9525591e9e301b68e93c4086 GIT binary patch literal 45053 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 Date: Tue, 14 Nov 2023 21:53:42 +0200 Subject: [PATCH 17/79] initial include of association class --- src/PacketSender.pro | 2 + src/association.cpp | 151 ++++++++++++++++++++++++++++++++++++++++++ src/association.h | 46 +++++++++++++ src/packetnetwork.cpp | 2 + 4 files changed, 201 insertions(+) create mode 100644 src/association.cpp create mode 100644 src/association.h diff --git a/src/PacketSender.pro b/src/PacketSender.pro index 6812c530..f5b86008 100755 --- a/src/PacketSender.pro +++ b/src/PacketSender.pro @@ -18,6 +18,7 @@ TRANSLATIONS += languages/packetsender_en.ts \ languages/packetsender_it.ts SOURCES += mainwindow.cpp \ + association.cpp \ languagechooser.cpp \ panel.cpp \ sendpacketbutton.cpp \ @@ -35,6 +36,7 @@ SOURCES += mainwindow.cpp \ persistenthttp.cpp HEADERS += mainwindow.h \ + association.h \ languagechooser.h \ panel.h \ sendpacketbutton.h \ diff --git a/src/association.cpp b/src/association.cpp new file mode 100644 index 00000000..28e2ba5a --- /dev/null +++ b/src/association.cpp @@ -0,0 +1,151 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "association.h" + +DtlsAssociation::DtlsAssociation(const QHostAddress &address, quint16 port, + const QString &connectionName) + : name(connectionName), + crypto(QSslSocket::SslClientMode) +{ + //! [1] + auto configuration = QSslConfiguration::defaultDtlsConfiguration(); + configuration.setPeerVerifyMode(QSslSocket::VerifyNone); + crypto.setPeer(address, port); + crypto.setDtlsConfiguration(configuration); + //! [1] + + //! [2] + connect(&crypto, &QDtls::handshakeTimeout, this, &DtlsAssociation::handshakeTimeout); + //! [2] + connect(&crypto, &QDtls::pskRequired, this, &DtlsAssociation::pskRequired); + //! [3] + socket.connectToHost(address.toString(), port); + //! [3] + //! [13] + connect(&socket, &QUdpSocket::readyRead, this, &DtlsAssociation::readyRead); + //! [13] + //! [4] + pingTimer.setInterval(5000); + connect(&pingTimer, &QTimer::timeout, this, &DtlsAssociation::pingTimeout); + //! [4] +} + +//! [12] +DtlsAssociation::~DtlsAssociation() +{ + if (crypto.isConnectionEncrypted()) + crypto.shutdown(&socket); +} +//! [12] + +//! [5] +void DtlsAssociation::startHandshake() +{ + if (socket.state() != QAbstractSocket::ConnectedState) { + emit infoMessage(tr("%1: connecting UDP socket first ...").arg(name)); + connect(&socket, &QAbstractSocket::connected, this, &DtlsAssociation::udpSocketConnected); + return; + } + + if (!crypto.doHandshake(&socket)) + emit errorMessage(tr("%1: failed to start a handshake - %2").arg(name, crypto.dtlsErrorString())); + else + emit infoMessage(tr("%1: starting a handshake").arg(name)); +} +//! [5] + +void DtlsAssociation::udpSocketConnected() +{ + emit infoMessage(tr("%1: UDP socket is now in ConnectedState, continue with handshake ...").arg(name)); + startHandshake(); +} + +void DtlsAssociation::readyRead() +{ + if (socket.pendingDatagramSize() <= 0) { + emit warningMessage(tr("%1: spurious read notification?").arg(name)); + return; + } + + //! [6] + QByteArray dgram(socket.pendingDatagramSize(), Qt::Uninitialized); + const qint64 bytesRead = socket.readDatagram(dgram.data(), dgram.size()); + if (bytesRead <= 0) { + emit warningMessage(tr("%1: spurious read notification?").arg(name)); + return; + } + + dgram.resize(bytesRead); + //! [6] + //! [7] + if (crypto.isConnectionEncrypted()) { + const QByteArray plainText = crypto.decryptDatagram(&socket, dgram); + if (plainText.size()) { + emit serverResponse(name, dgram, plainText); + return; + } + + if (crypto.dtlsError() == QDtlsError::RemoteClosedConnectionError) { + emit errorMessage(tr("%1: shutdown alert received").arg(name)); + socket.close(); + pingTimer.stop(); + return; + } + + emit warningMessage(tr("%1: zero-length datagram received?").arg(name)); + } else { + //! [7] + //! [8] + if (!crypto.doHandshake(&socket, dgram)) { + emit errorMessage(tr("%1: handshake error - %2").arg(name, crypto.dtlsErrorString())); + return; + } + //! [8] + + //! [9] + if (crypto.isConnectionEncrypted()) { + emit infoMessage(tr("%1: encrypted connection established!").arg(name)); + pingTimer.start(); + pingTimeout(); + } else { + //! [9] + emit infoMessage(tr("%1: continuing with handshake ...").arg(name)); + } + } +} + +//! [11] +void DtlsAssociation::handshakeTimeout() +{ + emit warningMessage(tr("%1: handshake timeout, trying to re-transmit").arg(name)); + if (!crypto.handleTimeout(&socket)) + emit errorMessage(tr("%1: failed to re-transmit - %2").arg(name, crypto.dtlsErrorString())); +} +//! [11] + +//! [14] +void DtlsAssociation::pskRequired(QSslPreSharedKeyAuthenticator *auth) +{ + Q_ASSERT(auth); + + emit infoMessage(tr("%1: providing pre-shared key ...").arg(name)); + auth->setIdentity(name.toLatin1()); + auth->setPreSharedKey(QByteArrayLiteral("\x1a\x2b\x3c\x4d\x5e\x6f")); +} +//! [14] + +//! [10] +void DtlsAssociation::pingTimeout() +{ + static const QString message = QStringLiteral("I am %1, please, accept our ping %2"); + const qint64 written = crypto.writeDatagramEncrypted(&socket, message.arg(name).arg(ping).toLatin1()); + if (written <= 0) { + emit errorMessage(tr("%1: failed to send a ping - %2").arg(name, crypto.dtlsErrorString())); + pingTimer.stop(); + return; + } + + ++ping; +} +//! [10] diff --git a/src/association.h b/src/association.h new file mode 100644 index 00000000..4822d7c0 --- /dev/null +++ b/src/association.h @@ -0,0 +1,46 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +#ifndef ASSOCIATION_H +#define ASSOCIATION_H + +#include +#include + +//! [0] +class DtlsAssociation : public QObject +{ + Q_OBJECT + +public: + DtlsAssociation(const QHostAddress &address, quint16 port, + const QString &connectionName); + ~DtlsAssociation(); + void startHandshake(); + +signals: + void errorMessage(const QString &message); + void warningMessage(const QString &message); + void infoMessage(const QString &message); + void serverResponse(const QString &clientInfo, const QByteArray &datagraam, + const QByteArray &plainText); + +private slots: + void udpSocketConnected(); + void readyRead(); + void handshakeTimeout(); + void pskRequired(QSslPreSharedKeyAuthenticator *auth); + void pingTimeout(); + +private: + QString name; + QUdpSocket socket; + QDtls crypto; + + QTimer pingTimer; + unsigned ping = 0; + + Q_DISABLE_COPY(DtlsAssociation) +}; +//! [0] + +#endif // ASSOCIATION_H diff --git a/src/packetnetwork.cpp b/src/packetnetwork.cpp index 7f8f8a5e..45054e45 100755 --- a/src/packetnetwork.cpp +++ b/src/packetnetwork.cpp @@ -24,6 +24,8 @@ #include #include #include +#include "association.h" + #ifdef CONSOLE_BUILD class QMessageBox { public: From f30c96397b8e616a4ce23d40ec0f201ca25116c7 Mon Sep 17 00:00:00 2001 From: israel Date: Tue, 14 Nov 2023 22:16:04 +0200 Subject: [PATCH 18/79] client of isDtls() can create handshake with server example --- src/packetnetwork.cpp | 35 +++++++++-------------------------- 1 file changed, 9 insertions(+), 26 deletions(-) diff --git a/src/packetnetwork.cpp b/src/packetnetwork.cpp index 45054e45..f0298c20 100755 --- a/src/packetnetwork.cpp +++ b/src/packetnetwork.cpp @@ -835,32 +835,15 @@ void PacketNetwork::packetToSend(Packet sendpacket) } //the vector of cmdComponents contains: dataStr, toIp, toPort, sslPrivateKeyPath, sslLocalCertificatePath, sslCaFullPath std::vector cmdComponents = getCmdInput(sendpacket, settings); - //status is determine if the connection established or doesn't - DWORD status = 0; - DWORD& statusRef = status; - //opensslPath stored the openssl s_client commands depends if the session is open or close - QString opensslPath; - //if the user want to leave the session open - if(settings.value("leaveSessionOpen").toString() == "true"){ - //if the session is closed, create session key and save it: - if (!MainWindow::isSessionOpen){ - MainWindow::isSessionOpen = true; - opensslPath ="cmd.exe /c (type nul > session.pem) & (echo "+ cmdComponents[0] + " | openssl s_client -dtls1_2 -connect " + cmdComponents[1] + ":" + cmdComponents[2] + " -sess_out session.pem -key \"" + cmdComponents[3] + "\" -cert \"" + cmdComponents[4] +"\" -CAfile \"" + cmdComponents[5] + "\" -verify 2 -cipher " + cmdComponents[6] +")"; - execCmd(opensslPath, statusRef, sendpacket); - //if the session is open, use the session key that has been saved: - } else{ - //opensslPath ="cmd.exe /c echo "+ data + " | openssl s_client -dtls1_2 -connect " + sendpacket.toIP + ":" + QString::number(sendpacket.port)+" -sess_in session.pem"; - opensslPath ="cmd.exe /c echo "+ cmdComponents[0] + " | openssl s_client -dtls1_2 -connect " + cmdComponents[1] + ":" + cmdComponents[2] +" -sess_in session.pem"; - execCmd(opensslPath, statusRef, sendpacket); - } - } - //if the user doesn't want to leave the session open - else{ - MainWindow::isSessionOpen = false; - opensslPath ="cmd.exe /c (del session.pem) & (echo "+ cmdComponents[0] + " | openssl s_client -dtls1_2 -connect " + cmdComponents[1] + ":" + cmdComponents[2] + " -key \"" + cmdComponents[3] + "\" -cert \"" + cmdComponents[4] +"\" -CAfile \"" + cmdComponents[5] + "\" -verify 2 -cipher " + cmdComponents[6] +")"; - execCmd(opensslPath, statusRef, sendpacket); - } - emit packetSent(sendpacket); + //qdtls + const QString ipAddress = "127.0.0.1"; + QHostAddress ipAddressHost; + ipAddressHost.setAddress(ipAddress); + quint16 port = 22334; + QString connName = "clientDtls"; + DtlsAssociation *dtlsAssociation = new DtlsAssociation(ipAddressHost, port, connName); + dtlsAssociation->startHandshake(); + //dtlsAssociation.readyRead(); } if (sendpacket.isUDP()) { From 6b5639584df9c06deac97505945249f42a46c6e9 Mon Sep 17 00:00:00 2001 From: israel Date: Sun, 19 Nov 2023 17:04:28 +0200 Subject: [PATCH 19/79] the client verifies the server and provides its own parameters, the client also can send the default massage to the server. the client also choose the cipher suite: AES-256-GCM-SHA384 --- src/association.cpp | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/association.cpp b/src/association.cpp index 28e2ba5a..094aed6b 100644 --- a/src/association.cpp +++ b/src/association.cpp @@ -9,10 +9,39 @@ DtlsAssociation::DtlsAssociation(const QHostAddress &address, quint16 port, crypto(QSslSocket::SslClientMode) { //! [1] + //auto configuration = QSslConfiguration::defaultDtlsConfiguration(); + + ////////////////////// + QFile certFile("C:/Users/israe/OneDrive - ort braude college of engineering/rsa_encryption/client-signed-cert.pem"); + if(!certFile.open(QIODevice::ReadOnly)){ + return; + } + QSslCertificate certificate(&certFile, QSsl::Pem); + + QFile keyFile("C:/Users/israe/OneDrive - ort braude college of engineering/rsa_encryption/client-key.pem"); + if(!keyFile.open(QIODevice::ReadOnly)){ + return; + } + QSslKey privateKey(&keyFile, QSsl::Rsa); // Or QSsl::Ec if your key is ECDSA + + QFile caCertFile("C:/Users/israe/OneDrive - ort braude college of engineering/rsa_encryption/ca-signed-cert/signed-cert.pem"); + if(!caCertFile.open(QIODevice::ReadOnly)){ + return; + } + QSslCertificate caCertificate(&caCertFile, QSsl::Pem); + auto configuration = QSslConfiguration::defaultDtlsConfiguration(); + configuration.setCiphers("AES256-GCM-SHA384"); + + configuration.setLocalCertificate(certificate); + configuration.setPrivateKey(privateKey); + configuration.setCaCertificates(QList() << caCertificate); + ////////////////////// + configuration.setPeerVerifyMode(QSslSocket::VerifyNone); crypto.setPeer(address, port); crypto.setDtlsConfiguration(configuration); + //! [1] //! [2] From 2d5501e087337bc432dbaa471303a0f628753205 Mon Sep 17 00:00:00 2001 From: israel Date: Mon, 20 Nov 2023 11:31:01 +0200 Subject: [PATCH 20/79] the QSslConfiguration object was extracted from the constructor to be a member of the association class --- src/association.cpp | 2 +- src/association.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/association.cpp b/src/association.cpp index 094aed6b..aca69160 100644 --- a/src/association.cpp +++ b/src/association.cpp @@ -30,7 +30,7 @@ DtlsAssociation::DtlsAssociation(const QHostAddress &address, quint16 port, } QSslCertificate caCertificate(&caCertFile, QSsl::Pem); - auto configuration = QSslConfiguration::defaultDtlsConfiguration(); + //auto configuration = QSslConfiguration::defaultDtlsConfiguration(); configuration.setCiphers("AES256-GCM-SHA384"); configuration.setLocalCertificate(certificate); diff --git a/src/association.h b/src/association.h index 4822d7c0..c837e795 100644 --- a/src/association.h +++ b/src/association.h @@ -16,6 +16,7 @@ class DtlsAssociation : public QObject const QString &connectionName); ~DtlsAssociation(); void startHandshake(); + QSslConfiguration configuration = QSslConfiguration::defaultDtlsConfiguration(); signals: void errorMessage(const QString &message); From 231f504265d0a8c977b41bc25227d6af05f15507 Mon Sep 17 00:00:00 2001 From: israel Date: Mon, 20 Nov 2023 11:53:52 +0200 Subject: [PATCH 21/79] all the data for the connection creation is taken from the user, except the massage of the user --- src/association.cpp | 52 ++++++++++++++++++++++++++++++++++++++++++- src/association.h | 2 ++ src/packetnetwork.cpp | 8 ++++--- 3 files changed, 58 insertions(+), 4 deletions(-) diff --git a/src/association.cpp b/src/association.cpp index aca69160..ef6a5e99 100644 --- a/src/association.cpp +++ b/src/association.cpp @@ -31,7 +31,7 @@ DtlsAssociation::DtlsAssociation(const QHostAddress &address, quint16 port, QSslCertificate caCertificate(&caCertFile, QSsl::Pem); //auto configuration = QSslConfiguration::defaultDtlsConfiguration(); - configuration.setCiphers("AES256-GCM-SHA384"); + //configuration.setCiphers("AES256-GCM-SHA384"); configuration.setLocalCertificate(certificate); configuration.setPrivateKey(privateKey); @@ -178,3 +178,53 @@ void DtlsAssociation::pingTimeout() ++ping; } //! [10] +void DtlsAssociation::setCipher(QString chosenCipher) { + configuration.setCiphers(chosenCipher); + crypto.setDtlsConfiguration(configuration); +} + +void DtlsAssociation::setKeyCertAndCaCert(QString keyPath, QString certPath, QString caPath) { + QFile keyFile(keyPath); + QFile certFile(certPath); + QFile caFile(caPath); + if (certFile.open(QIODevice::ReadOnly) && keyFile.open(QIODevice::ReadOnly) && caFile.open(QIODevice::ReadOnly)) { + QSslKey privateKey(&keyFile, QSsl::Rsa, QSsl::Pem); + QSslCertificate certificate(&certFile, QSsl::Pem); + QSslCertificate caCertificate(&caFile, QSsl::Pem); + + configuration.setPrivateKey(privateKey); + configuration.setLocalCertificate(certificate); + configuration.setCaCertificates(QList() << caCertificate); + + crypto.setDtlsConfiguration(configuration); + } + else { + //QDebug("Error loading certs or key"); + } +} +//////////////////////// +//QFile certFile("C:/Users/israe/OneDrive - ort braude college of engineering/rsa_encryption/client-signed-cert.pem"); +//if(!certFile.open(QIODevice::ReadOnly)){ +// return; +//} +//QSslCertificate certificate(&certFile, QSsl::Pem); + +//QFile keyFile("C:/Users/israe/OneDrive - ort braude college of engineering/rsa_encryption/client-key.pem"); +//if(!keyFile.open(QIODevice::ReadOnly)){ +// return; +//} +//QSslKey privateKey(&keyFile, QSsl::Rsa); // Or QSsl::Ec if your key is ECDSA + +//QFile caCertFile("C:/Users/israe/OneDrive - ort braude college of engineering/rsa_encryption/ca-signed-cert/signed-cert.pem"); +//if(!caCertFile.open(QIODevice::ReadOnly)){ +// return; +//} +//QSslCertificate caCertificate(&caCertFile, QSsl::Pem); + +////auto configuration = QSslConfiguration::defaultDtlsConfiguration(); +//configuration.setCiphers("AES256-GCM-SHA384"); + +//configuration.setLocalCertificate(certificate); +//configuration.setPrivateKey(privateKey); +//configuration.setCaCertificates(QList() << caCertificate); +//////////////////////// diff --git a/src/association.h b/src/association.h index c837e795..ee7930fe 100644 --- a/src/association.h +++ b/src/association.h @@ -16,6 +16,8 @@ class DtlsAssociation : public QObject const QString &connectionName); ~DtlsAssociation(); void startHandshake(); + void setKeyCertAndCaCert(QString keyPath, QString certPath, QString caPath); + void setCipher(QString chosenCipher); QSslConfiguration configuration = QSslConfiguration::defaultDtlsConfiguration(); signals: diff --git a/src/packetnetwork.cpp b/src/packetnetwork.cpp index f0298c20..d38182b6 100755 --- a/src/packetnetwork.cpp +++ b/src/packetnetwork.cpp @@ -833,15 +833,17 @@ void PacketNetwork::packetToSend(Packet sendpacket) if (settings.status() != QSettings::NoError) { sendpacket.errorString ="Can't open settings file."; } - //the vector of cmdComponents contains: dataStr, toIp, toPort, sslPrivateKeyPath, sslLocalCertificatePath, sslCaFullPath + //the vector of cmdComponents contains: dataStr, toIp, toPort, sslPrivateKeyPath, sslLocalCertificatePath, sslCaFullPath, chosen cipher std::vector cmdComponents = getCmdInput(sendpacket, settings); //qdtls - const QString ipAddress = "127.0.0.1"; + const QString ipAddress = cmdComponents[1]; QHostAddress ipAddressHost; ipAddressHost.setAddress(ipAddress); - quint16 port = 22334; + quint16 port = cmdComponents[2].toUShort(); QString connName = "clientDtls"; DtlsAssociation *dtlsAssociation = new DtlsAssociation(ipAddressHost, port, connName); + dtlsAssociation->setKeyCertAndCaCert(cmdComponents[3],cmdComponents[4], cmdComponents[5]); + dtlsAssociation->setCipher(cmdComponents[6]); dtlsAssociation->startHandshake(); //dtlsAssociation.readyRead(); } From 493b9ef28fae2166f9c68ce8eb6f2c0918edf0cc Mon Sep 17 00:00:00 2001 From: israel Date: Mon, 20 Nov 2023 22:13:46 +0200 Subject: [PATCH 22/79] The client can show in the trafficLog table a massage that the server send to him --- src/association.cpp | 2 +- src/association.h | 2 +- src/mainwindow.cpp | 3 +++ src/packetnetwork.cpp | 36 ++++++++++++++++++++++++++++++++++++ src/packetnetwork.h | 3 +++ 5 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/association.cpp b/src/association.cpp index ef6a5e99..2e97bd34 100644 --- a/src/association.cpp +++ b/src/association.cpp @@ -111,7 +111,7 @@ void DtlsAssociation::readyRead() if (crypto.isConnectionEncrypted()) { const QByteArray plainText = crypto.decryptDatagram(&socket, dgram); if (plainText.size()) { - emit serverResponse(name, dgram, plainText); + emit serverResponse(name, dgram, plainText, crypto.peerAddress(), crypto.peerPort()); return; } diff --git a/src/association.h b/src/association.h index ee7930fe..9fd92576 100644 --- a/src/association.h +++ b/src/association.h @@ -25,7 +25,7 @@ class DtlsAssociation : public QObject void warningMessage(const QString &message); void infoMessage(const QString &message); void serverResponse(const QString &clientInfo, const QByteArray &datagraam, - const QByteArray &plainText); + const QByteArray &plainText, QHostAddress peerAddress, quint16 peerPort); private slots: void udpSocketConnected(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f484c658..9c71e606 100755 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -174,6 +174,9 @@ MainWindow::MainWindow(QWidget *parent) : connect(&packetNetwork, SIGNAL(packetSent(Packet)), this, SLOT(toTrafficLog(Packet))); + connect(&packetNetwork, SIGNAL(packetReceived(Packet)), this, SLOT(toTrafficLog(Packet))); + + if( !QFile::exists(PACKETSFILE)) { // Packets file does not exist. Load starter set. QFile starterfile(":/starter_set.json"); diff --git a/src/packetnetwork.cpp b/src/packetnetwork.cpp index d38182b6..f9eaf62d 100755 --- a/src/packetnetwork.cpp +++ b/src/packetnetwork.cpp @@ -842,10 +842,13 @@ void PacketNetwork::packetToSend(Packet sendpacket) quint16 port = cmdComponents[2].toUShort(); QString connName = "clientDtls"; DtlsAssociation *dtlsAssociation = new DtlsAssociation(ipAddressHost, port, connName); + connect(dtlsAssociation, &DtlsAssociation::serverResponse, this, &PacketNetwork::addServerResponse); dtlsAssociation->setKeyCertAndCaCert(cmdComponents[3],cmdComponents[4], cmdComponents[5]); dtlsAssociation->setCipher(cmdComponents[6]); dtlsAssociation->startHandshake(); //dtlsAssociation.readyRead(); + emit packetSent(sendpacket); + //emit packetReceivedECHO(sendpacket); } if (sendpacket.isUDP()) { @@ -1154,3 +1157,36 @@ QList PacketNetwork::allTCPServers() return theServers; } +void PacketNetwork::addServerResponse(const QString &clientInfo, const QByteArray &datagram, const QByteArray &plainText, QHostAddress peerAddress, quint16 peerPort) +{ + Packet recPacket; + recPacket.init(); + recPacket.fromIP = peerAddress.toString(); + QString string = QString::fromUtf8(plainText); + recPacket.hexString = string; + recPacket.toIP = QString::number(peerPort); + recPacket.errorString = "none"; + recPacket.tcpOrUdp = "DTLS"; + + emit packetReceived(recPacket); + + +// FROMDB_STRING(toIP); +// FROMDB_UINT(port); +// FROMDB_FLOAT(repeat); +// FROMDB_UINT(fromPort); +// FROMDB_STRING(tcpOrUdp); +// FROMDB_STRING(hexString); +// FROMDB_STRING(requestPath); +// packets.append(packet); +// static const QString messageColor = QStringLiteral("DarkMagenta"); +// static const QString formatter = QStringLiteral("
---------------" +// "
%1 received a DTLS datagram:
%2" +// "
As plain text:
%3"); + +// const QString html = formatter.arg(clientInfo, QString::fromUtf8(datagram.toHex(' ')), +// QString::fromUtf8(plainText)); +// ui->serverMessages->insertHtml(colorizer.arg(messageColor, html)); + //connect(&packetNetwork, SIGNAL(packetSent(Packet)), + // this, SLOT(toTrafficLog(Packet))); +} diff --git a/src/packetnetwork.h b/src/packetnetwork.h index f40aebe8..fd38c213 100755 --- a/src/packetnetwork.h +++ b/src/packetnetwork.h @@ -110,12 +110,15 @@ public slots: void readPendingDatagrams(); void disconnected(); void packetToSend(Packet sendpacket); + void addServerResponse(const QString &clientInfo, const QByteArray &datagram, const QByteArray &plainText, QHostAddress peerAddress, quint16 peerPort); + private slots: void httpFinished(QNetworkReply* pReply); void httpError(QNetworkRequest* pReply); void sslErrorsSlot(QNetworkReply *reply, const QList &errors); + private: //mapping of joined multicast groups //format is 239.255.120.19:5009, 239.255.120.23:5009 From 011b28c340f6d87d166b8dc4251ecfd23e244e9b Mon Sep 17 00:00:00 2001 From: israel Date: Tue, 21 Nov 2023 12:56:23 +0200 Subject: [PATCH 23/79] the client port of the trafficLog fixed. Added a dtls button to the status bar --- src/association.h | 3 +- src/mainwindow.cpp | 50 ++++++++++++++++-- src/mainwindow.h | 3 ++ src/packetnetwork.cpp | 115 +++++++++++++++++++++++++++++++++--------- src/packetnetwork.h | 5 ++ src/settings.cpp | 3 +- 6 files changed, 148 insertions(+), 31 deletions(-) diff --git a/src/association.h b/src/association.h index 9fd92576..a2ca4e96 100644 --- a/src/association.h +++ b/src/association.h @@ -12,6 +12,8 @@ class DtlsAssociation : public QObject Q_OBJECT public: + QUdpSocket socket; + DtlsAssociation(const QHostAddress &address, quint16 port, const QString &connectionName); ~DtlsAssociation(); @@ -36,7 +38,6 @@ private slots: private: QString name; - QUdpSocket socket; QDtls crypto; QTimer pingTimer; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 9c71e606..3af0f101 100755 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -262,6 +262,16 @@ MainWindow::MainWindow(QWidget *parent) : stopResendingButton->hide(); + dtlsServerStatus = new QPushButton("DTLS:" + packetNetwork.getDTLSPortString()); + themeTheButton(dtlsServerStatus); + dtlsServerStatus->setIcon(QIcon(UDPRXICON)); + + connect(dtlsServerStatus, SIGNAL(clicked()), + this, SLOT(toggleDTLSServer())); + + + statusBar()->insertPermanentWidget(2, dtlsServerStatus); + udpServerStatus = new QPushButton("UDP:" + packetNetwork.getUDPPortString()); themeTheButton(udpServerStatus); udpServerStatus->setIcon(QIcon(UDPRXICON)); @@ -270,7 +280,7 @@ MainWindow::MainWindow(QWidget *parent) : this, SLOT(toggleUDPServer())); - statusBar()->insertPermanentWidget(2, udpServerStatus); + statusBar()->insertPermanentWidget(3, udpServerStatus); //updatewidget @@ -290,15 +300,17 @@ MainWindow::MainWindow(QWidget *parent) : this, SLOT(toggleSSLServer())); - statusBar()->insertPermanentWidget(3, tcpServerStatus); + statusBar()->insertPermanentWidget(4, tcpServerStatus); + + + statusBar()->insertPermanentWidget(5, sslServerStatus); - statusBar()->insertPermanentWidget(4, sslServerStatus); //ipmode toggle IPmodeButton = new QPushButton("IPv4 Mode"); themeTheButton(IPmodeButton); - statusBar()->insertPermanentWidget(5, IPmodeButton); + statusBar()->insertPermanentWidget(6, IPmodeButton); setIPMode(); @@ -497,6 +509,13 @@ MainWindow::MainWindow(QWidget *parent) : +void MainWindow::toggleDTLSServer() +{ + QSettings settings(SETTINGSFILE, QSettings::IniFormat); + settings.setValue("dtlsServerEnable", !settings.value("dtlsServerEnable", true).toBool()); + applyNetworkSettings(); +} + void MainWindow::toggleUDPServer() { QSettings settings(SETTINGSFILE, QSettings::IniFormat); @@ -766,6 +785,28 @@ void MainWindow::toTrafficLog(Packet logPacket) } +void MainWindow::DTLSServerStatus() +{ + + if (packetNetwork.DTLSListening()) { + QString ports = packetNetwork.getUDPPortString(); + int portcount = packetNetwork.getUDPPortsBound().size(); + dtlsServerStatus->setToolTip(ports); + if(portcount > 3) { + dtlsServerStatus->setText("DTLS: " + QString::number(portcount) + tr(" Ports")); + } else { + dtlsServerStatus->setText("DTLS:" + ports); + } + + } else { + dtlsServerStatus->setText(tr("DTLS Server Disabled")); + + } + + //updatewidget + + +} void MainWindow::UDPServerStatus() { @@ -1945,6 +1986,7 @@ void MainWindow::applyNetworkSettings() ui->persistentTCPCheck->setChecked(settings.value("persistentTCPCheck", false).toBool()); on_persistentTCPCheck_clicked(ui->persistentTCPCheck->isChecked()); + DTLSServerStatus(); UDPServerStatus(); TCPServerStatus(); SSLServerStatus(); diff --git a/src/mainwindow.h b/src/mainwindow.h index e371072f..03584572 100755 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -86,6 +86,7 @@ class MainWindow : public QMainWindow QPushButton *generateDNLink(); void populateTableRow(int rowCounter, Packet tempPacket); void removePacketfromMemory(Packet thepacket); + void DTLSServerStatus(); void UDPServerStatus(); void TCPServerStatus(); int findColumnIndex(QListWidget *lw, QString search); @@ -102,6 +103,7 @@ class MainWindow : public QMainWindow void toTrafficLog(Packet logPacket); void cancelResends(); void applyNetworkSettings(); + void toggleDTLSServer(); void toggleUDPServer(); void toggleTCPServer(); void toggleSSLServer(); @@ -250,6 +252,7 @@ class MainWindow : public QMainWindow QTimer refreshTimer; QTimer slowRefreshTimer; bool tableActive; + QPushButton * dtlsServerStatus; QPushButton * udpServerStatus; QPushButton * tcpServerStatus; QPushButton * sslServerStatus; diff --git a/src/packetnetwork.cpp b/src/packetnetwork.cpp index f9eaf62d..b97d3223 100755 --- a/src/packetnetwork.cpp +++ b/src/packetnetwork.cpp @@ -150,6 +150,20 @@ QString PacketNetwork::getIPmode() return ipMode; } +bool PacketNetwork::DTLSListening() +{ + QUdpSocket * dtls; + QDEBUGVAR(dtlsServers.size()); + foreach(dtls, dtlsServers) { + QDEBUGVAR(dtls->state()); + if(dtls->state() == QAbstractSocket::BoundState) { + //if(udp->state() == QAbstractSocket::ConnectedState) { + return true; + } + } + return false; +} + bool PacketNetwork::UDPListening() { QUdpSocket * udp; @@ -224,7 +238,7 @@ void PacketNetwork::init() static bool erroronce = false; - + dtlsServers.clear(); tcpServers.clear(); udpServers.clear(); sslServers.clear(); @@ -236,11 +250,14 @@ void PacketNetwork::init() QSettings settings(SETTINGSFILE, QSettings::IniFormat); - QList udpPortList, tcpPortList, sslPortList; + QList udpPortList, tcpPortList, sslPortList, dtlsPortList; + int dtlsPort = 0; int udpPort = 0; int tcpPort = 0; int sslPort = 0; + settings.setValue("dtlsPort", "12346"); + dtlsPortList = Settings::portsToIntList(settings.value("dtlsPort", "0").toString()); udpPortList = Settings::portsToIntList(settings.value("udpPort", "0").toString()); tcpPortList = Settings::portsToIntList(settings.value("tcpPort", "0").toString()); sslPortList = Settings::portsToIntList(settings.value("sslPort", "0").toString()); @@ -261,8 +278,47 @@ void PacketNetwork::init() #endif - QUdpSocket *udpSocket; + QUdpSocket *udpSocket, *dtlsSocket; ThreadedTCPServer *ssl, *tcp; + foreach (dtlsPort, dtlsPortList) { + + + dtlsSocket = new QUdpSocket(this); + + bool bindResult = dtlsSocket->bind( + IPV4_OR_IPV6 + , dtlsPort); + + dtlsSocket->setSocketOption(QAbstractSocket::MulticastTtlOption, 128); + + if ((!bindResult) && (!erroronce)) { + QDEBUGVAR(dtlsPort); + erroronce = true; + if ((dtlsPort < 1024) && (dtlsPort > 0)) { + QString msgText = lowPortText; + msgText.replace("[PORT]", QString::number(dtlsPort)); + msgBoxBindError.setText(msgText); + msgBoxBindError.exec(); + + } else { + QString msgText = portConsumedText; + msgText.replace("[PORT]", QString::number(dtlsPort)); + msgBoxBindError.setText(msgText); + msgBoxBindError.exec(); + + } + dtlsSocket->close(); + dtlsSocket->deleteLater(); + + } + + if(bindResult) { + dtlsServers.append(dtlsSocket); + } + + } + reJoinMulticast(); + foreach (udpPort, udpPortList) { @@ -278,7 +334,7 @@ void PacketNetwork::init() if ((!bindResult) && (!erroronce)) { QDEBUGVAR(udpPort); erroronce = true; - if (udpPort < 1024 && udpPort > 0) { + if ((udpPort < 1024) && (udpPort > 0)) { QString msgText = lowPortText; msgText.replace("[PORT]", QString::number(udpPort)); msgBoxBindError.setText(msgText); @@ -353,6 +409,7 @@ void PacketNetwork::init() sendResponse = settings.value("sendReponse", false).toBool(); responseData = (settings.value("responseHex", "")).toString(); + activateDTLS = settings.value("dtlsServerEnable", true).toBool(); activateUDP = settings.value("udpServerEnable", true).toBool(); activateTCP = settings.value("tcpServerEnable", true).toBool(); activateSSL = settings.value("sslServerEnable", true).toBool(); @@ -420,6 +477,28 @@ void PacketNetwork::init() //TODO add timed event feature? +QList PacketNetwork::getDTLSPortsBound() +{ + QList pList; + pList.clear(); + QUdpSocket * dtls; + foreach (dtls, dtlsServers) { + if(dtls->BoundState == QAbstractSocket::BoundState) { + if(dtls->localAddress().isMulticast()) { + QDEBUG() << "This udp address is multicast"; + } + pList.append(dtls->localPort()); + } + } + return pList; + +} + +QString PacketNetwork::getDTLSPortString() +{ + + return Settings::intListToPorts(getDTLSPortsBound()); +} QList PacketNetwork::getUDPPortsBound() { @@ -828,6 +907,7 @@ void PacketNetwork::packetToSend(Packet sendpacket) sendpacket.name = sendpacket.timestamp.toString(DATETIMEFORMAT); if(sendpacket.isDTLS()){ + //QUdpSocket * sendUDP //open settings file in order to get the ssl valuse of the packet QSettings settings(SETTINGSFILE, QSettings::IniFormat); if (settings.status() != QSettings::NoError) { @@ -842,11 +922,15 @@ void PacketNetwork::packetToSend(Packet sendpacket) quint16 port = cmdComponents[2].toUShort(); QString connName = "clientDtls"; DtlsAssociation *dtlsAssociation = new DtlsAssociation(ipAddressHost, port, connName); + dtlsAssociation->socket; + sendpacket.fromPort = dtlsAssociation->socket.localPort(); + connect(dtlsAssociation, &DtlsAssociation::serverResponse, this, &PacketNetwork::addServerResponse); dtlsAssociation->setKeyCertAndCaCert(cmdComponents[3],cmdComponents[4], cmdComponents[5]); dtlsAssociation->setCipher(cmdComponents[6]); dtlsAssociation->startHandshake(); //dtlsAssociation.readyRead(); + //sendpacket.port = cmdComponents[2]; emit packetSent(sendpacket); //emit packetReceivedECHO(sendpacket); } @@ -1162,31 +1246,12 @@ void PacketNetwork::addServerResponse(const QString &clientInfo, const QByteArra Packet recPacket; recPacket.init(); recPacket.fromIP = peerAddress.toString(); + recPacket.fromPort = peerPort; QString string = QString::fromUtf8(plainText); recPacket.hexString = string; - recPacket.toIP = QString::number(peerPort); + //recPacket.toIP = ; recPacket.errorString = "none"; recPacket.tcpOrUdp = "DTLS"; emit packetReceived(recPacket); - - -// FROMDB_STRING(toIP); -// FROMDB_UINT(port); -// FROMDB_FLOAT(repeat); -// FROMDB_UINT(fromPort); -// FROMDB_STRING(tcpOrUdp); -// FROMDB_STRING(hexString); -// FROMDB_STRING(requestPath); -// packets.append(packet); -// static const QString messageColor = QStringLiteral("DarkMagenta"); -// static const QString formatter = QStringLiteral("
---------------" -// "
%1 received a DTLS datagram:
%2" -// "
As plain text:
%3"); - -// const QString html = formatter.arg(clientInfo, QString::fromUtf8(datagram.toHex(' ')), -// QString::fromUtf8(plainText)); -// ui->serverMessages->insertHtml(colorizer.arg(messageColor, html)); - //connect(&packetNetwork, SIGNAL(packetSent(Packet)), - // this, SLOT(toTrafficLog(Packet))); } diff --git a/src/packetnetwork.h b/src/packetnetwork.h index fd38c213..a1478913 100755 --- a/src/packetnetwork.h +++ b/src/packetnetwork.h @@ -50,10 +50,12 @@ class PacketNetwork : public QObject QString debugQByteArray(QByteArray debugArray); + QString getDTLSPortString(); QString getUDPPortString(); QString getTCPPortString(); QString getSSLPortString(); + QList getDTLSPortsBound(); QList getUDPPortsBound(); QList getTCPPortsBound(); QList getSSLPortsBound(); @@ -66,6 +68,7 @@ class PacketNetwork : public QObject QString responseData; bool sendResponse; bool sendSmartResponse; + bool activateDTLS; bool activateUDP; bool activateTCP; bool activateSSL; @@ -77,6 +80,7 @@ class PacketNetwork : public QObject void setIPmode(int mode); static QString getIPmode(); + bool DTLSListening(); bool UDPListening(); bool TCPListening(); bool SSLListening(); @@ -138,6 +142,7 @@ private slots: QList tcpServers; QList sslServers; QList udpServers; + QList dtlsServers; }; diff --git a/src/settings.cpp b/src/settings.cpp index 37cfdeb1..056957a8 100755 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -398,7 +398,7 @@ void Settings::on_buttonBox_accepted() } - + //settings.setValue("dtlsPort", intListToPorts(dtlsList)); settings.setValue("udpPort", intListToPorts(udpList)); settings.setValue("tcpPort", intListToPorts(tcpList)); settings.setValue("sslPort", intListToPorts(sslList)); @@ -407,6 +407,7 @@ void Settings::on_buttonBox_accepted() settings.setValue("responseName", ui->responsePacketBox->currentText().trimmed()); settings.setValue("responseHex", ui->hexResponseEdit->text().trimmed()); + //settings.setValue("dtlsServerEnable", ui->dtlsServerEnableCheck->isChecked()); settings.setValue("udpServerEnable", ui->udpServerEnableCheck->isChecked()); settings.setValue("tcpServerEnable", ui->tcpServerEnableCheck->isChecked()); settings.setValue("sslServerEnable", ui->sslServerEnableCheck->isChecked()); From df4f629f6d59f79c07ac43cb6141a4b179b932e2 Mon Sep 17 00:00:00 2001 From: israel Date: Tue, 21 Nov 2023 17:48:49 +0200 Subject: [PATCH 24/79] Inside the trafficLog all the info about the server respond is correct --- src/association.cpp | 2 +- src/association.h | 5 +++-- src/packetnetwork.cpp | 25 +++++++++++++++++-------- src/packetnetwork.h | 3 ++- 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/association.cpp b/src/association.cpp index 2e97bd34..bd75ddd9 100644 --- a/src/association.cpp +++ b/src/association.cpp @@ -111,7 +111,7 @@ void DtlsAssociation::readyRead() if (crypto.isConnectionEncrypted()) { const QByteArray plainText = crypto.decryptDatagram(&socket, dgram); if (plainText.size()) { - emit serverResponse(name, dgram, plainText, crypto.peerAddress(), crypto.peerPort()); + emit serverResponse(name, dgram, plainText, crypto.peerAddress(), crypto.peerPort(), socket.localPort()); return; } diff --git a/src/association.h b/src/association.h index a2ca4e96..f5b42961 100644 --- a/src/association.h +++ b/src/association.h @@ -13,6 +13,7 @@ class DtlsAssociation : public QObject public: QUdpSocket socket; + QString name; DtlsAssociation(const QHostAddress &address, quint16 port, const QString &connectionName); @@ -27,7 +28,7 @@ class DtlsAssociation : public QObject void warningMessage(const QString &message); void infoMessage(const QString &message); void serverResponse(const QString &clientInfo, const QByteArray &datagraam, - const QByteArray &plainText, QHostAddress peerAddress, quint16 peerPort); + const QByteArray &plainText, QHostAddress peerAddress, quint16 peerPort, quint16 clientPort); private slots: void udpSocketConnected(); @@ -37,7 +38,7 @@ private slots: void pingTimeout(); private: - QString name; + QDtls crypto; QTimer pingTimer; diff --git a/src/packetnetwork.cpp b/src/packetnetwork.cpp index b97d3223..7935013c 100755 --- a/src/packetnetwork.cpp +++ b/src/packetnetwork.cpp @@ -25,6 +25,8 @@ #include #include #include "association.h" +#include + #ifdef CONSOLE_BUILD class QMessageBox { @@ -907,6 +909,7 @@ void PacketNetwork::packetToSend(Packet sendpacket) sendpacket.name = sendpacket.timestamp.toString(DATETIMEFORMAT); if(sendpacket.isDTLS()){ + //sendpacket.fromPort = sendUDP->localPort(); //QUdpSocket * sendUDP //open settings file in order to get the ssl valuse of the packet QSettings settings(SETTINGSFILE, QSettings::IniFormat); @@ -920,8 +923,9 @@ void PacketNetwork::packetToSend(Packet sendpacket) QHostAddress ipAddressHost; ipAddressHost.setAddress(ipAddress); quint16 port = cmdComponents[2].toUShort(); - QString connName = "clientDtls"; - DtlsAssociation *dtlsAssociation = new DtlsAssociation(ipAddressHost, port, connName); + //+QString::number(sendpacket.fromPort); + //QString test = QString::number(sendpacket.fromPort); + DtlsAssociation *dtlsAssociation = new DtlsAssociation(ipAddressHost, port, sendpacket.fromIP); dtlsAssociation->socket; sendpacket.fromPort = dtlsAssociation->socket.localPort(); @@ -1241,15 +1245,20 @@ QList PacketNetwork::allTCPServers() return theServers; } -void PacketNetwork::addServerResponse(const QString &clientInfo, const QByteArray &datagram, const QByteArray &plainText, QHostAddress peerAddress, quint16 peerPort) +void PacketNetwork::addServerResponse(const QString &clientAddress, const QByteArray &datagram, const QByteArray &plainText, QHostAddress serverAddress, quint16 serverPort, quint16 userPort) { + //ned to fix the "to port" field + //find a way do reach the client data (maybe use the clientInfo) inorder to present the "ToAddress" and "To Port" fields in the traffic log area + //QStringList clientIpAndPort = clientInfo.split(':', Qt::KeepEmptyParts); + Packet recPacket; recPacket.init(); - recPacket.fromIP = peerAddress.toString(); - recPacket.fromPort = peerPort; - QString string = QString::fromUtf8(plainText); - recPacket.hexString = string; - //recPacket.toIP = ; + recPacket.fromIP = serverAddress.toString(); + recPacket.fromPort = serverPort; + QString massageFromTheOtherPeer = QString::fromUtf8(plainText); + recPacket.hexString = massageFromTheOtherPeer; + recPacket.toIP = clientAddress; + recPacket.port = userPort; recPacket.errorString = "none"; recPacket.tcpOrUdp = "DTLS"; diff --git a/src/packetnetwork.h b/src/packetnetwork.h index a1478913..0a4aa209 100755 --- a/src/packetnetwork.h +++ b/src/packetnetwork.h @@ -114,7 +114,8 @@ public slots: void readPendingDatagrams(); void disconnected(); void packetToSend(Packet sendpacket); - void addServerResponse(const QString &clientInfo, const QByteArray &datagram, const QByteArray &plainText, QHostAddress peerAddress, quint16 peerPort); + //void addServerResponse(const QString &clientInfo, const QByteArray &datagram, const QByteArray &plainText, QHostAddress peerAddress, quint16 peerPort); + void addServerResponse(const QString &clientInfo, const QByteArray &datagram, const QByteArray &plainText, QHostAddress serverAddress, quint16 serverPort, quint16 userPort); private slots: From 2b48def88b13f20fe4fcfe1d6ab0a96b7bf5efd0 Mon Sep 17 00:00:00 2001 From: israel Date: Tue, 21 Nov 2023 18:13:11 +0200 Subject: [PATCH 25/79] the traffic log and the combobox are updated --- src/mainwindow.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 3af0f101..c0e9a569 100755 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -38,6 +38,8 @@ #include +#include + #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) @@ -73,6 +75,12 @@ MainWindow::MainWindow(QWidget *parent) : ui->setupUi(this); cipherCb = ui->cipherCb; + //add the combobox the correct cipher suites + QList ciphers = QSslConfiguration::supportedCiphers(); + for (const QSslCipher &cipher : ciphers) { + cipherCb->addItem(cipher.name()); + } + if ( ui->udptcpComboBox->currentText().toLower() != "dtls"){ ui->loadKeyButton->hide(); ui->loadCertButton->hide(); From de81eaf826474c0fa32b559f5b7f5d664d667ae1 Mon Sep 17 00:00:00 2001 From: israel Date: Wed, 22 Nov 2023 10:51:54 +0200 Subject: [PATCH 26/79] The client send the massage contant single time --- src/association.cpp | 19 +++++++++++-------- src/association.h | 2 ++ src/packetnetwork.cpp | 3 +++ 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/association.cpp b/src/association.cpp index bd75ddd9..b7817b25 100644 --- a/src/association.cpp +++ b/src/association.cpp @@ -167,15 +167,18 @@ void DtlsAssociation::pskRequired(QSslPreSharedKeyAuthenticator *auth) //! [10] void DtlsAssociation::pingTimeout() { - static const QString message = QStringLiteral("I am %1, please, accept our ping %2"); - const qint64 written = crypto.writeDatagramEncrypted(&socket, message.arg(name).arg(ping).toLatin1()); - if (written <= 0) { - emit errorMessage(tr("%1: failed to send a ping - %2").arg(name, crypto.dtlsErrorString())); - pingTimer.stop(); - return; + //static const QString message = QStringLiteral("I am %1, please, accept our ping %2"); + //const qint64 written = crypto.writeDatagramEncrypted(&socket, message.arg(name).arg(ping).toLatin1()); + if(this->newMassageToSend){ + const qint64 written = crypto.writeDatagramEncrypted(&socket, massageToSend.toLatin1()); + if (written <= 0) { + emit errorMessage(tr("%1: failed to send a ping - %2").arg(name, crypto.dtlsErrorString())); + pingTimer.stop(); + return; + } + this->newMassageToSend = false; + ++ping; } - - ++ping; } //! [10] void DtlsAssociation::setCipher(QString chosenCipher) { diff --git a/src/association.h b/src/association.h index f5b42961..fe345ab6 100644 --- a/src/association.h +++ b/src/association.h @@ -12,6 +12,8 @@ class DtlsAssociation : public QObject Q_OBJECT public: + bool newMassageToSend = false; + QString massageToSend; QUdpSocket socket; QString name; diff --git a/src/packetnetwork.cpp b/src/packetnetwork.cpp index 7935013c..d7a4b228 100755 --- a/src/packetnetwork.cpp +++ b/src/packetnetwork.cpp @@ -919,6 +919,7 @@ void PacketNetwork::packetToSend(Packet sendpacket) //the vector of cmdComponents contains: dataStr, toIp, toPort, sslPrivateKeyPath, sslLocalCertificatePath, sslCaFullPath, chosen cipher std::vector cmdComponents = getCmdInput(sendpacket, settings); //qdtls + const QString ipAddress = cmdComponents[1]; QHostAddress ipAddressHost; ipAddressHost.setAddress(ipAddress); @@ -926,6 +927,8 @@ void PacketNetwork::packetToSend(Packet sendpacket) //+QString::number(sendpacket.fromPort); //QString test = QString::number(sendpacket.fromPort); DtlsAssociation *dtlsAssociation = new DtlsAssociation(ipAddressHost, port, sendpacket.fromIP); + dtlsAssociation->newMassageToSend = true; + dtlsAssociation->massageToSend = cmdComponents[0]; dtlsAssociation->socket; sendpacket.fromPort = dtlsAssociation->socket.localPort(); From 1d5cd313e8c610f5c0d24780d31e4ddf4ec1dcc2 Mon Sep 17 00:00:00 2001 From: israel Date: Wed, 22 Nov 2023 18:11:40 +0200 Subject: [PATCH 27/79] beginning of using dtls thread --- src/PacketSender.pro | 2 + src/association.h | 7 ++- src/dtlsthread.cpp | 44 ++++++++++++++++ src/dtlsthread.h | 49 ++++++++++++++++++ src/packetnetwork.cpp | 102 ++++++++++++++++++++++++++----------- src/packetnetwork.h | 2 + src/persistentconnection.h | 2 + 7 files changed, 176 insertions(+), 32 deletions(-) create mode 100644 src/dtlsthread.cpp create mode 100644 src/dtlsthread.h diff --git a/src/PacketSender.pro b/src/PacketSender.pro index f5b86008..6f4a565b 100755 --- a/src/PacketSender.pro +++ b/src/PacketSender.pro @@ -19,6 +19,7 @@ TRANSLATIONS += languages/packetsender_en.ts \ SOURCES += mainwindow.cpp \ association.cpp \ + dtlsthread.cpp \ languagechooser.cpp \ panel.cpp \ sendpacketbutton.cpp \ @@ -37,6 +38,7 @@ SOURCES += mainwindow.cpp \ HEADERS += mainwindow.h \ association.h \ + dtlsthread.h \ languagechooser.h \ panel.h \ sendpacketbutton.h \ diff --git a/src/association.h b/src/association.h index fe345ab6..adda5c15 100644 --- a/src/association.h +++ b/src/association.h @@ -16,6 +16,8 @@ class DtlsAssociation : public QObject QString massageToSend; QUdpSocket socket; QString name; + QDtls crypto; + DtlsAssociation(const QHostAddress &address, quint16 port, const QString &connectionName); @@ -32,16 +34,17 @@ class DtlsAssociation : public QObject void serverResponse(const QString &clientInfo, const QByteArray &datagraam, const QByteArray &plainText, QHostAddress peerAddress, quint16 peerPort, quint16 clientPort); +public slots: + void pingTimeout(); private slots: void udpSocketConnected(); void readyRead(); void handshakeTimeout(); void pskRequired(QSslPreSharedKeyAuthenticator *auth); - void pingTimeout(); + private: - QDtls crypto; QTimer pingTimer; unsigned ping = 0; diff --git a/src/dtlsthread.cpp b/src/dtlsthread.cpp new file mode 100644 index 00000000..a3a602e4 --- /dev/null +++ b/src/dtlsthread.cpp @@ -0,0 +1,44 @@ +#include "dtlsthread.h" +#include "packet.h" + +Dtlsthread::Dtlsthread(Packet sendPacket, QObject *parent) + : QThread(parent), sendPacket(sendPacket) +{} + +Dtlsthread::~Dtlsthread() { + // Destructor implementation (can be empty for this example) +} + + +void Dtlsthread::run() +{ + // Implement the run function + // If run() is meant to be pure virtual, then this implementation should be in a subclass. +} + + + + + + + + + + + +//#include "dtlsthread.h" +//#include + + + +//Dtlsthread::Dtlsthread(Packet sendPacket, QObject *parent) +// : QThread(parent), sendPacket(sendPacket) +//{ + + +//} + +//void Dtlsthread::run() { +//} + + diff --git a/src/dtlsthread.h b/src/dtlsthread.h new file mode 100644 index 00000000..e14f808a --- /dev/null +++ b/src/dtlsthread.h @@ -0,0 +1,49 @@ +#ifndef BASETHREAD_H +#define BASETHREAD_H + +#include +#include "packet.h" + +class Dtlsthread : public QThread +{ + Q_OBJECT + + +public: + Dtlsthread(Packet sendPacket, QObject *parent); + virtual ~Dtlsthread(); + Packet sendPacket; + void run() override; // Pure virtual function making this class abstract + void sendPersistant(); + + + +}; + +#endif // BASETHREAD_H + + + + +//#ifndef DTLSTHREAD_H +//#define DTLSTHREAD_H +//#include "packet.h" +//#include + +//class Dtlsthread : public QThread +//{ +// Q_OBJECT + +//public: +// Packet sendPacket; + + +// Dtlsthread(Packet sendPacket, QObject *parent); +// void run() override; // Entry point for the thread +//// void setParameters(int param); // Setter for parameters + +////private: +//// int m_param; // Parameter to be used in the thread +//}; +//#endif + diff --git a/src/packetnetwork.cpp b/src/packetnetwork.cpp index d7a4b228..d240366f 100755 --- a/src/packetnetwork.cpp +++ b/src/packetnetwork.cpp @@ -26,6 +26,8 @@ #include #include "association.h" #include +#include "dtlsthread.h" + #ifdef CONSOLE_BUILD @@ -908,40 +910,80 @@ void PacketNetwork::packetToSend(Packet sendpacket) sendpacket.timestamp = QDateTime::currentDateTime(); sendpacket.name = sendpacket.timestamp.toString(DATETIMEFORMAT); + if(sendpacket.isDTLS()){ - //sendpacket.fromPort = sendUDP->localPort(); - //QUdpSocket * sendUDP - //open settings file in order to get the ssl valuse of the packet - QSettings settings(SETTINGSFILE, QSettings::IniFormat); - if (settings.status() != QSettings::NoError) { - sendpacket.errorString ="Can't open settings file."; + if (sendpacket.persistent){ //spawn a window. + PersistentConnection * pcWindow = new PersistentConnection(); + Dtlsthread * thread = new Dtlsthread(sendpacket, this); + pcWindow->sendPacket = sendpacket; + pcWindow->init(); + pcWindow->dthread = thread; + + + QDEBUG() << ": thread Connection attempt " << + connect(pcWindow, SIGNAL(persistentPacketSend(Packet)), thread, SLOT(sendPersistant(Packet))); +// << connect(pcWindow, SIGNAL(closeConnection()), thread, SLOT(closeConnection())) +// << connect(thread, SIGNAL(connectStatus(QString)), pcWindow, SLOT(statusReceiver(QString))) +// << connect(thread, SIGNAL(packetSent(Packet)), pcWindow, SLOT(packetSentSlot(Packet))); + + +// QDEBUG() << connect(thread, SIGNAL(packetReceived(Packet)), this, SLOT(packetReceivedECHO(Packet))) +// << connect(thread, SIGNAL(toStatusBar(QString, int, bool)), this, SLOT(toStatusBarECHO(QString, int, bool))) +// << connect(thread, SIGNAL(packetSent(Packet)), this, SLOT(packetSentECHO(Packet))); + + + //connect(&packetNetwork, SIGNAL(packetSent(Packet)), + // this, SLOT(toTrafficLog(Packet))); + + pcWindow->show(); + thread->start(); + + + //Network manager will manage this thread so the UI window doesn't need to. + dtlsthreadList.append(thread); + + return; + }else{ + //sendpacket.fromPort = sendUDP->localPort(); + //QUdpSocket * sendUDP + //open settings file in order to get the ssl valuse of the packet + QSettings settings(SETTINGSFILE, QSettings::IniFormat); + if (settings.status() != QSettings::NoError) { + sendpacket.errorString ="Can't open settings file."; + } + //the vector of cmdComponents contains: dataStr, toIp, toPort, sslPrivateKeyPath, sslLocalCertificatePath, sslCaFullPath, chosen cipher + std::vector cmdComponents = getCmdInput(sendpacket, settings); + //qdtls + + const QString ipAddress = cmdComponents[1]; + QHostAddress ipAddressHost; + ipAddressHost.setAddress(ipAddress); + quint16 port = cmdComponents[2].toUShort(); + //+QString::number(sendpacket.fromPort); + //QString test = QString::number(sendpacket.fromPort); + DtlsAssociation *dtlsAssociation = new DtlsAssociation(ipAddressHost, port, sendpacket.fromIP); + dtlsAssociation->newMassageToSend = true; + dtlsAssociation->massageToSend = cmdComponents[0]; + dtlsAssociation->socket; + sendpacket.fromPort = dtlsAssociation->socket.localPort(); + + connect(dtlsAssociation, &DtlsAssociation::serverResponse, this, &PacketNetwork::addServerResponse); + dtlsAssociation->setKeyCertAndCaCert(cmdComponents[3],cmdComponents[4], cmdComponents[5]); + dtlsAssociation->setCipher(cmdComponents[6]); + dtlsAssociation->startHandshake(); + //Sleep(10000); +// if(dtlsAssociation->crypto.isConnectionEncrypted()){ +// dtlsAssociation->pingTimeout(); + +// } + //sendpacket.port = cmdComponents[2]; + emit packetSent(sendpacket); + //emit packetReceivedECHO(sendpacket); } - //the vector of cmdComponents contains: dataStr, toIp, toPort, sslPrivateKeyPath, sslLocalCertificatePath, sslCaFullPath, chosen cipher - std::vector cmdComponents = getCmdInput(sendpacket, settings); - //qdtls - - const QString ipAddress = cmdComponents[1]; - QHostAddress ipAddressHost; - ipAddressHost.setAddress(ipAddress); - quint16 port = cmdComponents[2].toUShort(); - //+QString::number(sendpacket.fromPort); - //QString test = QString::number(sendpacket.fromPort); - DtlsAssociation *dtlsAssociation = new DtlsAssociation(ipAddressHost, port, sendpacket.fromIP); - dtlsAssociation->newMassageToSend = true; - dtlsAssociation->massageToSend = cmdComponents[0]; - dtlsAssociation->socket; - sendpacket.fromPort = dtlsAssociation->socket.localPort(); - - connect(dtlsAssociation, &DtlsAssociation::serverResponse, this, &PacketNetwork::addServerResponse); - dtlsAssociation->setKeyCertAndCaCert(cmdComponents[3],cmdComponents[4], cmdComponents[5]); - dtlsAssociation->setCipher(cmdComponents[6]); - dtlsAssociation->startHandshake(); - //dtlsAssociation.readyRead(); - //sendpacket.port = cmdComponents[2]; - emit packetSent(sendpacket); - //emit packetReceivedECHO(sendpacket); + } + if (sendpacket.isUDP()) { QUdpSocket * sendUDP; bool oneoff = false; diff --git a/src/packetnetwork.h b/src/packetnetwork.h index 0a4aa209..faf27fa8 100755 --- a/src/packetnetwork.h +++ b/src/packetnetwork.h @@ -32,6 +32,7 @@ #include #include #include +#include "dtlsthread.h" @@ -132,6 +133,7 @@ private slots: QList allTCPServers(); QList httpList; + QList dtlsthreadList; QList tcpthreadList; #ifdef CONSOLE_BUILD diff --git a/src/persistentconnection.h b/src/persistentconnection.h index e6ace23e..45fa4031 100755 --- a/src/persistentconnection.h +++ b/src/persistentconnection.h @@ -7,6 +7,7 @@ #include "packet.h" #include "tcpthread.h" +#include "dtlsthread.h" namespace Ui { @@ -26,6 +27,7 @@ class PersistentConnection : public QDialog Packet sendPacket; Packet reSendPacket; TCPThread *thread; + Dtlsthread *dthread; static const QString RESEND_BUTTON_STYLE; From fd8f339816c9786772a430c11f5042d1c030cceb Mon Sep 17 00:00:00 2001 From: israel Date: Sun, 26 Nov 2023 20:53:34 +0200 Subject: [PATCH 28/79] not working: adding the working last ps version a code that uses class of dtlsthread --- src/association.cpp | 17 +++-- src/association.h | 12 ++-- src/dtlsthread.cpp | 118 ++++++++++++++++++++++++++++------- src/dtlsthread.h | 11 +++- src/packetnetwork.cpp | 73 +++++----------------- src/persistentconnection.cpp | 4 +- 6 files changed, 143 insertions(+), 92 deletions(-) diff --git a/src/association.cpp b/src/association.cpp index b7817b25..c8b16139 100644 --- a/src/association.cpp +++ b/src/association.cpp @@ -2,9 +2,10 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause #include "association.h" +#include "packet.h" DtlsAssociation::DtlsAssociation(const QHostAddress &address, quint16 port, - const QString &connectionName) + const QString &connectionName, Packet packetToSend) : name(connectionName), crypto(QSslSocket::SslClientMode) { @@ -56,7 +57,7 @@ DtlsAssociation::DtlsAssociation(const QHostAddress &address, quint16 port, //! [13] //! [4] pingTimer.setInterval(5000); - connect(&pingTimer, &QTimer::timeout, this, &DtlsAssociation::pingTimeout); + //connect(&pingTimer, &QTimer::timeout, this, &DtlsAssociation::pingTimeout); //! [4] } @@ -135,8 +136,8 @@ void DtlsAssociation::readyRead() //! [9] if (crypto.isConnectionEncrypted()) { emit infoMessage(tr("%1: encrypted connection established!").arg(name)); - pingTimer.start(); - pingTimeout(); + //writeMassage(); + emit handShakeComplited(packetToSend, this); } else { //! [9] emit infoMessage(tr("%1: continuing with handshake ...").arg(name)); @@ -165,11 +166,16 @@ void DtlsAssociation::pskRequired(QSslPreSharedKeyAuthenticator *auth) //! [14] //! [10] +//! +//! +//! only for ping massage void DtlsAssociation::pingTimeout() { + //static const QString message = QStringLiteral("I am %1, please, accept our ping %2"); //const qint64 written = crypto.writeDatagramEncrypted(&socket, message.arg(name).arg(ping).toLatin1()); if(this->newMassageToSend){ + emit handShakeComplited(packetToSend, this); const qint64 written = crypto.writeDatagramEncrypted(&socket, massageToSend.toLatin1()); if (written <= 0) { emit errorMessage(tr("%1: failed to send a ping - %2").arg(name, crypto.dtlsErrorString())); @@ -181,6 +187,9 @@ void DtlsAssociation::pingTimeout() } } //! [10] + + + void DtlsAssociation::setCipher(QString chosenCipher) { configuration.setCiphers(chosenCipher); crypto.setDtlsConfiguration(configuration); diff --git a/src/association.h b/src/association.h index adda5c15..a1d5eb25 100644 --- a/src/association.h +++ b/src/association.h @@ -5,6 +5,7 @@ #include #include +#include "packet.h" //! [0] class DtlsAssociation : public QObject @@ -12,15 +13,15 @@ class DtlsAssociation : public QObject Q_OBJECT public: + QDtls crypto; bool newMassageToSend = false; QString massageToSend; QUdpSocket socket; QString name; - QDtls crypto; - + Packet packetToSend; DtlsAssociation(const QHostAddress &address, quint16 port, - const QString &connectionName); + const QString &connectionName, Packet packetToSend); ~DtlsAssociation(); void startHandshake(); void setKeyCertAndCaCert(QString keyPath, QString certPath, QString caPath); @@ -28,19 +29,20 @@ class DtlsAssociation : public QObject QSslConfiguration configuration = QSslConfiguration::defaultDtlsConfiguration(); signals: + void handShakeComplited(Packet packetToSend, DtlsAssociation* dtlsAssociation); void errorMessage(const QString &message); void warningMessage(const QString &message); void infoMessage(const QString &message); void serverResponse(const QString &clientInfo, const QByteArray &datagraam, const QByteArray &plainText, QHostAddress peerAddress, quint16 peerPort, quint16 clientPort); -public slots: - void pingTimeout(); private slots: void udpSocketConnected(); void readyRead(); void handshakeTimeout(); void pskRequired(QSslPreSharedKeyAuthenticator *auth); + void pingTimeout(); + //void writeMassage(); private: diff --git a/src/dtlsthread.cpp b/src/dtlsthread.cpp index a3a602e4..c9e39b09 100644 --- a/src/dtlsthread.cpp +++ b/src/dtlsthread.cpp @@ -1,8 +1,13 @@ + #include "dtlsthread.h" #include "packet.h" +#include "association.h" +#include "packetnetwork.h" +//#include "QSettings" + -Dtlsthread::Dtlsthread(Packet sendPacket, QObject *parent) - : QThread(parent), sendPacket(sendPacket) +Dtlsthread::Dtlsthread(Packet sendpacket, QObject *parent) + : QThread(parent), sendpacket(sendpacket) {} Dtlsthread::~Dtlsthread() { @@ -12,33 +17,98 @@ Dtlsthread::~Dtlsthread() { void Dtlsthread::run() { - // Implement the run function - // If run() is meant to be pure virtual, then this implementation should be in a subclass. + QSettings settings(SETTINGSFILE, QSettings::IniFormat); + if (settings.status() != QSettings::NoError) { + sendpacket.errorString ="Can't open settings file."; + } + //the vector of cmdComponents contains: dataStr, toIp, toPort, sslPrivateKeyPath, sslLocalCertificatePath, sslCaFullPath, chosen cipher + std::vector cmdComponents = getCmdInput(sendpacket, settings); + //qdtls + + const QString ipAddress = cmdComponents[1]; + QHostAddress ipAddressHost; + ipAddressHost.setAddress(ipAddress); + quint16 port = cmdComponents[2].toUShort(); + //+QString::number(sendpacket.fromPort); + //QString test = QString::number(sendpacket.fromPort); + DtlsAssociation *dtlsAssociation = new DtlsAssociation(ipAddressHost, port, sendpacket.fromIP, sendpacket); + dtlsAssociation->newMassageToSend = true; + dtlsAssociation->massageToSend = cmdComponents[0]; + dtlsAssociation->socket; + sendpacket.fromPort = dtlsAssociation->socket.localPort(); + connect(dtlsAssociation, &DtlsAssociation::serverResponse, this, &Dtlsthread::addServerResponse); + dtlsAssociation->setKeyCertAndCaCert(cmdComponents[3],cmdComponents[4], cmdComponents[5]); + dtlsAssociation->setCipher(cmdComponents[6]); + //dtlsAssociation->startHandshake(); + connect(dtlsAssociation, &DtlsAssociation::handShakeComplited,this, &Dtlsthread::writeMassage); + dtlsAssociation->startHandshake(); } +std::vector Dtlsthread::getCmdInput(Packet sendpacket, QSettings& settings){ + //the array of cmdComponents: dataStr, toIp, toPort, sslPrivateKeyPath, sslLocalCertificatePath, sslCaFullPath + std::vector cmdComponents; + + //get the data of the packet + cmdComponents.push_back(QString::fromUtf8(sendpacket.getByteArray())); + cmdComponents.push_back(sendpacket.toIP); + cmdComponents.push_back(QString::number(sendpacket.port)); + + //get the pathes for verification from the settings + cmdComponents.push_back(settings.value("sslPrivateKeyPath", "default").toString()); + cmdComponents.push_back(settings.value("sslLocalCertificatePath", "default").toString()); + QString sslCaPath = settings.value("sslCaPath", "default").toString(); + + //get the full path to to ca-signed-cert.pem file + QDir dir(sslCaPath); + if (dir.exists()) { + QStringList nameFilters; + nameFilters << "*.pem"; // Filter for .txt files + + dir.setNameFilters(nameFilters); + QStringList fileList = dir.entryList(); + + if (!fileList.isEmpty()) { + // Select the first file that matches the filter + cmdComponents.push_back(dir.filePath(fileList.first())); + } else { + qDebug() << "No matching files found."; + } + } else { + qDebug() << "Directory does not exist."; + } + cmdComponents.push_back(settings.value("cipher", "AES256-GCM-SHA384").toString()); + return cmdComponents; +} +void Dtlsthread::addServerResponse(const QString &clientAddress, const QByteArray &datagram, const QByteArray &plainText, QHostAddress serverAddress, quint16 serverPort, quint16 userPort) +{ + //ned to fix the "to port" field + //find a way do reach the client data (maybe use the clientInfo) inorder to present the "ToAddress" and "To Port" fields in the traffic log area + //QStringList clientIpAndPort = clientInfo.split(':', Qt::KeepEmptyParts); + + Packet recPacket; + recPacket.init(); + recPacket.fromIP = serverAddress.toString(); + recPacket.fromPort = serverPort; + QString massageFromTheOtherPeer = QString::fromUtf8(plainText); + recPacket.hexString = massageFromTheOtherPeer; + recPacket.toIP = clientAddress; + recPacket.port = userPort; + recPacket.errorString = "none"; + recPacket.tcpOrUdp = "DTLS"; + + ///emit packetReceived(recPacket); +} +void Dtlsthread::writeMassage(Packet packetToSend, DtlsAssociation* dtlsAssociation){ - - - - - - -//#include "dtlsthread.h" -//#include - - - -//Dtlsthread::Dtlsthread(Packet sendPacket, QObject *parent) -// : QThread(parent), sendPacket(sendPacket) -//{ - - -//} - -//void Dtlsthread::run() { -//} + //emit handShakeComplited(packetToSend, this); + const qint64 written = dtlsAssociation->crypto.writeDatagramEncrypted(&(dtlsAssociation->socket), dtlsAssociation->massageToSend.toLatin1()); + if (written <= 0) { + //emit errorMessage(tr("%1: failed to send a ping - %2").arg(name, crypto.dtlsErrorString())); + return; + } +} diff --git a/src/dtlsthread.h b/src/dtlsthread.h index e14f808a..77a059da 100644 --- a/src/dtlsthread.h +++ b/src/dtlsthread.h @@ -3,6 +3,8 @@ #include #include "packet.h" +#include "QSettings" +#include "association.h" class Dtlsthread : public QThread { @@ -12,9 +14,16 @@ class Dtlsthread : public QThread public: Dtlsthread(Packet sendPacket, QObject *parent); virtual ~Dtlsthread(); - Packet sendPacket; + std::vector dtlsAssociations; + + Packet sendpacket; void run() override; // Pure virtual function making this class abstract void sendPersistant(); + std::vector getCmdInput(Packet sendpacket, QSettings& settings); +public slots: + void writeMassage(Packet packetToSend, DtlsAssociation* dtlsAssociation); + void addServerResponse(const QString &clientInfo, const QByteArray &datagram, const QByteArray &plainText, QHostAddress serverAddress, quint16 serverPort, quint16 userPort); + diff --git a/src/packetnetwork.cpp b/src/packetnetwork.cpp index d240366f..71850f9a 100755 --- a/src/packetnetwork.cpp +++ b/src/packetnetwork.cpp @@ -912,19 +912,18 @@ void PacketNetwork::packetToSend(Packet sendpacket) if(sendpacket.isDTLS()){ - if (sendpacket.persistent){ //spawn a window. - PersistentConnection * pcWindow = new PersistentConnection(); - Dtlsthread * thread = new Dtlsthread(sendpacket, this); - pcWindow->sendPacket = sendpacket; - pcWindow->init(); - pcWindow->dthread = thread; + PersistentConnection * pcWindow = new PersistentConnection(); + Dtlsthread * thread = new Dtlsthread(sendpacket, this); + pcWindow->sendPacket = sendpacket; + pcWindow->init(); + pcWindow->dthread = thread; - QDEBUG() << ": thread Connection attempt " << - connect(pcWindow, SIGNAL(persistentPacketSend(Packet)), thread, SLOT(sendPersistant(Packet))); -// << connect(pcWindow, SIGNAL(closeConnection()), thread, SLOT(closeConnection())) -// << connect(thread, SIGNAL(connectStatus(QString)), pcWindow, SLOT(statusReceiver(QString))) -// << connect(thread, SIGNAL(packetSent(Packet)), pcWindow, SLOT(packetSentSlot(Packet))); + QDEBUG() /*<< ": thread Connection attempt " << + connect(pcWindow, SIGNAL(persistentPacketSend(Packet)), thread, SLOT(sendPersistant(Packet)));*/ + << connect(pcWindow, SIGNAL(closeConnection()), thread, SLOT(closeConnection())) + << connect(thread, SIGNAL(connectStatus(QString)), pcWindow, SLOT(statusReceiver(QString))) + << connect(thread, SIGNAL(packetSent(Packet)), pcWindow, SLOT(packetSentSlot(Packet))); // QDEBUG() << connect(thread, SIGNAL(packetReceived(Packet)), this, SLOT(packetReceivedECHO(Packet))) @@ -932,54 +931,16 @@ void PacketNetwork::packetToSend(Packet sendpacket) // << connect(thread, SIGNAL(packetSent(Packet)), this, SLOT(packetSentECHO(Packet))); - //connect(&packetNetwork, SIGNAL(packetSent(Packet)), - // this, SLOT(toTrafficLog(Packet))); - - pcWindow->show(); - thread->start(); + //connect(&packetNetwork, SIGNAL(packetSent(Packet)), + // this, SLOT(toTrafficLog(Packet))); + pcWindow->show(); + thread->start(); + thread->wait(); - //Network manager will manage this thread so the UI window doesn't need to. - dtlsthreadList.append(thread); - return; - }else{ - //sendpacket.fromPort = sendUDP->localPort(); - //QUdpSocket * sendUDP - //open settings file in order to get the ssl valuse of the packet - QSettings settings(SETTINGSFILE, QSettings::IniFormat); - if (settings.status() != QSettings::NoError) { - sendpacket.errorString ="Can't open settings file."; - } - //the vector of cmdComponents contains: dataStr, toIp, toPort, sslPrivateKeyPath, sslLocalCertificatePath, sslCaFullPath, chosen cipher - std::vector cmdComponents = getCmdInput(sendpacket, settings); - //qdtls - - const QString ipAddress = cmdComponents[1]; - QHostAddress ipAddressHost; - ipAddressHost.setAddress(ipAddress); - quint16 port = cmdComponents[2].toUShort(); - //+QString::number(sendpacket.fromPort); - //QString test = QString::number(sendpacket.fromPort); - DtlsAssociation *dtlsAssociation = new DtlsAssociation(ipAddressHost, port, sendpacket.fromIP); - dtlsAssociation->newMassageToSend = true; - dtlsAssociation->massageToSend = cmdComponents[0]; - dtlsAssociation->socket; - sendpacket.fromPort = dtlsAssociation->socket.localPort(); - - connect(dtlsAssociation, &DtlsAssociation::serverResponse, this, &PacketNetwork::addServerResponse); - dtlsAssociation->setKeyCertAndCaCert(cmdComponents[3],cmdComponents[4], cmdComponents[5]); - dtlsAssociation->setCipher(cmdComponents[6]); - dtlsAssociation->startHandshake(); - //Sleep(10000); -// if(dtlsAssociation->crypto.isConnectionEncrypted()){ -// dtlsAssociation->pingTimeout(); - -// } - //sendpacket.port = cmdComponents[2]; - emit packetSent(sendpacket); - //emit packetReceivedECHO(sendpacket); - } + //Network manager will manage this thread so the UI window doesn't need to. + dtlsthreadList.append(thread); } diff --git a/src/persistentconnection.cpp b/src/persistentconnection.cpp index e5f407a3..167b6c96 100755 --- a/src/persistentconnection.cpp +++ b/src/persistentconnection.cpp @@ -32,8 +32,8 @@ PersistentConnection::PersistentConnection(QWidget *parent) : QDEBUG(); sendPacket.clear(); - QDEBUG() << ": refreshTimer Connection attempt " << - connect(&refreshTimer, SIGNAL(timeout()), this, SLOT(refreshTimerTimeout())) + QDEBUG() /*<< ": refreshTimer Connection attempt " << + connect(&refreshTimer, SIGNAL(timeout()), this, SLOT(refreshTimerTimeout()))*/ << connect(this, SIGNAL(rejected()), this, SLOT(aboutToClose())) << connect(this, SIGNAL(accepted()), this, SLOT(aboutToClose())) << connect(this, SIGNAL(dialogIsClosing()), this, SLOT(aboutToClose())); From f3e9ca7cf2baf620be5bcdbe0d9e97ada7b3e455 Mon Sep 17 00:00:00 2001 From: israel Date: Mon, 27 Nov 2023 00:58:52 +0200 Subject: [PATCH 29/79] use of qdtls thread leads us before the client send its certs --- src/association.cpp | 20 +++++++++++++++++++- src/dtlsthread.cpp | 7 +++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/association.cpp b/src/association.cpp index c8b16139..464b8b0e 100644 --- a/src/association.cpp +++ b/src/association.cpp @@ -51,9 +51,14 @@ DtlsAssociation::DtlsAssociation(const QHostAddress &address, quint16 port, connect(&crypto, &QDtls::pskRequired, this, &DtlsAssociation::pskRequired); //! [3] socket.connectToHost(address.toString(), port); + socket.waitForConnected(); //! [3] //! [13] + //QEventLoop loop; connect(&socket, &QUdpSocket::readyRead, this, &DtlsAssociation::readyRead); + //loop.exec(); + + //! [13] //! [4] pingTimer.setInterval(5000); @@ -80,8 +85,11 @@ void DtlsAssociation::startHandshake() if (!crypto.doHandshake(&socket)) emit errorMessage(tr("%1: failed to start a handshake - %2").arg(name, crypto.dtlsErrorString())); - else + else{ + socket.waitForReadyRead(); emit infoMessage(tr("%1: starting a handshake").arg(name)); + } + } //! [5] @@ -91,8 +99,10 @@ void DtlsAssociation::udpSocketConnected() startHandshake(); } + void DtlsAssociation::readyRead() { + //QEventLoop loop; if (socket.pendingDatagramSize() <= 0) { emit warningMessage(tr("%1: spurious read notification?").arg(name)); return; @@ -140,11 +150,18 @@ void DtlsAssociation::readyRead() emit handShakeComplited(packetToSend, this); } else { //! [9] + //socket.waitForReadyRead(10000); + emit infoMessage(tr("%1: continuing with handshake ...").arg(name)); } + //socket.waitForReadyRead(10000); + } + //loop.exec(); + } + //! [11] void DtlsAssociation::handshakeTimeout() { @@ -192,6 +209,7 @@ void DtlsAssociation::pingTimeout() void DtlsAssociation::setCipher(QString chosenCipher) { configuration.setCiphers(chosenCipher); + //configuration.setProtocol(QSsl::DtlsV1_2); crypto.setDtlsConfiguration(configuration); } diff --git a/src/dtlsthread.cpp b/src/dtlsthread.cpp index c9e39b09..07d13ec7 100644 --- a/src/dtlsthread.cpp +++ b/src/dtlsthread.cpp @@ -21,6 +21,7 @@ void Dtlsthread::run() if (settings.status() != QSettings::NoError) { sendpacket.errorString ="Can't open settings file."; } + //the vector of cmdComponents contains: dataStr, toIp, toPort, sslPrivateKeyPath, sslLocalCertificatePath, sslCaFullPath, chosen cipher std::vector cmdComponents = getCmdInput(sendpacket, settings); //qdtls @@ -32,6 +33,8 @@ void Dtlsthread::run() //+QString::number(sendpacket.fromPort); //QString test = QString::number(sendpacket.fromPort); DtlsAssociation *dtlsAssociation = new DtlsAssociation(ipAddressHost, port, sendpacket.fromIP, sendpacket); + //dtlsAssociation->setProtocol(QSsl::DtlsV1_2); + dtlsAssociation->newMassageToSend = true; dtlsAssociation->massageToSend = cmdComponents[0]; dtlsAssociation->socket; @@ -41,7 +44,11 @@ void Dtlsthread::run() dtlsAssociation->setCipher(cmdComponents[6]); //dtlsAssociation->startHandshake(); connect(dtlsAssociation, &DtlsAssociation::handShakeComplited,this, &Dtlsthread::writeMassage); + //QEventLoop loop; dtlsAssociation->startHandshake(); + //loop.exec(); + + //dtlsAssociation->crypto.doHandshake(&(dtlsAssociation->socket)); } From 31bd770fc5ec715bd38e2d842e6f90ad63c092d2 Mon Sep 17 00:00:00 2001 From: israel Date: Mon, 27 Nov 2023 13:35:57 +0200 Subject: [PATCH 30/79] more try to use class of thread, while using waitForReadyRead() --- src/association.cpp | 10 +++++++--- src/dtlsthread.cpp | 3 ++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/association.cpp b/src/association.cpp index 464b8b0e..64b7fc9f 100644 --- a/src/association.cpp +++ b/src/association.cpp @@ -39,7 +39,7 @@ DtlsAssociation::DtlsAssociation(const QHostAddress &address, quint16 port, configuration.setCaCertificates(QList() << caCertificate); ////////////////////// - configuration.setPeerVerifyMode(QSslSocket::VerifyNone); + configuration.setPeerVerifyMode(QSslSocket::VerifyPeer); crypto.setPeer(address, port); crypto.setDtlsConfiguration(configuration); @@ -86,7 +86,9 @@ void DtlsAssociation::startHandshake() if (!crypto.doHandshake(&socket)) emit errorMessage(tr("%1: failed to start a handshake - %2").arg(name, crypto.dtlsErrorString())); else{ + //socket.waitForBytesWritten(); socket.waitForReadyRead(); + //crypto.doHandshake(&socket); emit infoMessage(tr("%1: starting a handshake").arg(name)); } @@ -103,6 +105,7 @@ void DtlsAssociation::udpSocketConnected() void DtlsAssociation::readyRead() { //QEventLoop loop; + QThread::sleep(2); if (socket.pendingDatagramSize() <= 0) { emit warningMessage(tr("%1: spurious read notification?").arg(name)); return; @@ -137,12 +140,14 @@ void DtlsAssociation::readyRead() } else { //! [7] //! [8] + if (!crypto.doHandshake(&socket, dgram)) { emit errorMessage(tr("%1: handshake error - %2").arg(name, crypto.dtlsErrorString())); return; } //! [8] - + socket.waitForReadyRead(); + crypto.doHandshake(&socket, dgram); //! [9] if (crypto.isConnectionEncrypted()) { emit infoMessage(tr("%1: encrypted connection established!").arg(name)); @@ -151,7 +156,6 @@ void DtlsAssociation::readyRead() } else { //! [9] //socket.waitForReadyRead(10000); - emit infoMessage(tr("%1: continuing with handshake ...").arg(name)); } //socket.waitForReadyRead(10000); diff --git a/src/dtlsthread.cpp b/src/dtlsthread.cpp index 07d13ec7..95b83c2b 100644 --- a/src/dtlsthread.cpp +++ b/src/dtlsthread.cpp @@ -40,12 +40,13 @@ void Dtlsthread::run() dtlsAssociation->socket; sendpacket.fromPort = dtlsAssociation->socket.localPort(); connect(dtlsAssociation, &DtlsAssociation::serverResponse, this, &Dtlsthread::addServerResponse); - dtlsAssociation->setKeyCertAndCaCert(cmdComponents[3],cmdComponents[4], cmdComponents[5]); + //dtlsAssociation->setKeyCertAndCaCert(cmdComponents[3],cmdComponents[4], cmdComponents[5]); dtlsAssociation->setCipher(cmdComponents[6]); //dtlsAssociation->startHandshake(); connect(dtlsAssociation, &DtlsAssociation::handShakeComplited,this, &Dtlsthread::writeMassage); //QEventLoop loop; dtlsAssociation->startHandshake(); + //dtlsAssociation->crypto.resumeHandshake(&(dtlsAssociation->socket)); //loop.exec(); //dtlsAssociation->crypto.doHandshake(&(dtlsAssociation->socket)); From 821b349f31cf3c719f2d59442e96540a455925e2 Mon Sep 17 00:00:00 2001 From: israel Date: Mon, 27 Nov 2023 15:55:39 +0200 Subject: [PATCH 31/79] working handshake while using dtlsthread class --- src/association.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/association.cpp b/src/association.cpp index 64b7fc9f..e7c4d74c 100644 --- a/src/association.cpp +++ b/src/association.cpp @@ -87,7 +87,12 @@ void DtlsAssociation::startHandshake() emit errorMessage(tr("%1: failed to start a handshake - %2").arg(name, crypto.dtlsErrorString())); else{ //socket.waitForBytesWritten(); - socket.waitForReadyRead(); + while(true){ + socket.waitForReadyRead(); + if(crypto.handshakeState() == QDtls::HandshakeComplete){ + break; + } + } //crypto.doHandshake(&socket); emit infoMessage(tr("%1: starting a handshake").arg(name)); } @@ -105,7 +110,7 @@ void DtlsAssociation::udpSocketConnected() void DtlsAssociation::readyRead() { //QEventLoop loop; - QThread::sleep(2); + //QThread::sleep(2); if (socket.pendingDatagramSize() <= 0) { emit warningMessage(tr("%1: spurious read notification?").arg(name)); return; @@ -146,7 +151,8 @@ void DtlsAssociation::readyRead() return; } //! [8] - socket.waitForReadyRead(); + + //socket.waitForReadyRead(); crypto.doHandshake(&socket, dgram); //! [9] if (crypto.isConnectionEncrypted()) { From 886828e788d543c43867e48fa8fa8351ebcb1fc1 Mon Sep 17 00:00:00 2001 From: israel Date: Mon, 27 Nov 2023 17:30:50 +0200 Subject: [PATCH 32/79] ps app can send single massgae to the server using saparate class for dtlsThread (without persistent option) --- src/association.cpp | 6 +++--- src/packetnetwork.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/association.cpp b/src/association.cpp index e7c4d74c..75450470 100644 --- a/src/association.cpp +++ b/src/association.cpp @@ -39,7 +39,7 @@ DtlsAssociation::DtlsAssociation(const QHostAddress &address, quint16 port, configuration.setCaCertificates(QList() << caCertificate); ////////////////////// - configuration.setPeerVerifyMode(QSslSocket::VerifyPeer); + configuration.setPeerVerifyMode(QSslSocket::VerifyNone); crypto.setPeer(address, port); crypto.setDtlsConfiguration(configuration); @@ -62,7 +62,7 @@ DtlsAssociation::DtlsAssociation(const QHostAddress &address, quint16 port, //! [13] //! [4] pingTimer.setInterval(5000); - //connect(&pingTimer, &QTimer::timeout, this, &DtlsAssociation::pingTimeout); + connect(&pingTimer, &QTimer::timeout, this, &DtlsAssociation::pingTimeout); //! [4] } @@ -89,7 +89,7 @@ void DtlsAssociation::startHandshake() //socket.waitForBytesWritten(); while(true){ socket.waitForReadyRead(); - if(crypto.handshakeState() == QDtls::HandshakeComplete){ + if(crypto.isConnectionEncrypted()){ break; } } diff --git a/src/packetnetwork.cpp b/src/packetnetwork.cpp index 71850f9a..31a0c503 100755 --- a/src/packetnetwork.cpp +++ b/src/packetnetwork.cpp @@ -936,7 +936,7 @@ void PacketNetwork::packetToSend(Packet sendpacket) pcWindow->show(); thread->start(); - thread->wait(); + //Network manager will manage this thread so the UI window doesn't need to. From d850810a591fa0b2ffd6b96553e0dd2777683422 Mon Sep 17 00:00:00 2001 From: israel Date: Mon, 27 Nov 2023 17:43:04 +0200 Subject: [PATCH 33/79] ps app can send single massgae to the server using saparate class for dtlsThread, without pingtimeout connect (without persistent option) --- src/association.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/association.cpp b/src/association.cpp index 75450470..d6a9e1cb 100644 --- a/src/association.cpp +++ b/src/association.cpp @@ -46,7 +46,7 @@ DtlsAssociation::DtlsAssociation(const QHostAddress &address, quint16 port, //! [1] //! [2] - connect(&crypto, &QDtls::handshakeTimeout, this, &DtlsAssociation::handshakeTimeout); + //connect(&crypto, &QDtls::handshakeTimeout, this, &DtlsAssociation::handshakeTimeout); //! [2] connect(&crypto, &QDtls::pskRequired, this, &DtlsAssociation::pskRequired); //! [3] From 70d4e6decdc22ce8f8a31f93a53d06cde1c84336 Mon Sep 17 00:00:00 2001 From: israel Date: Tue, 28 Nov 2023 21:22:31 +0200 Subject: [PATCH 34/79] the client can send massage using the dtlsthread class. the sent packet appears in the traffic-log --- src/association.cpp | 4 ++-- src/association.h | 2 +- src/dtlsthread.cpp | 21 ++++++++++++++++++++- src/dtlsthread.h | 8 ++++++-- src/packetnetwork.cpp | 6 ++++++ src/persistentconnection.cpp | 13 +++++++++++-- 6 files changed, 46 insertions(+), 8 deletions(-) diff --git a/src/association.cpp b/src/association.cpp index d6a9e1cb..2a652271 100644 --- a/src/association.cpp +++ b/src/association.cpp @@ -158,7 +158,7 @@ void DtlsAssociation::readyRead() if (crypto.isConnectionEncrypted()) { emit infoMessage(tr("%1: encrypted connection established!").arg(name)); //writeMassage(); - emit handShakeComplited(packetToSend, this); + emit handShakeComplited(); } else { //! [9] //socket.waitForReadyRead(10000); @@ -202,7 +202,7 @@ void DtlsAssociation::pingTimeout() //static const QString message = QStringLiteral("I am %1, please, accept our ping %2"); //const qint64 written = crypto.writeDatagramEncrypted(&socket, message.arg(name).arg(ping).toLatin1()); if(this->newMassageToSend){ - emit handShakeComplited(packetToSend, this); + emit handShakeComplited(); const qint64 written = crypto.writeDatagramEncrypted(&socket, massageToSend.toLatin1()); if (written <= 0) { emit errorMessage(tr("%1: failed to send a ping - %2").arg(name, crypto.dtlsErrorString())); diff --git a/src/association.h b/src/association.h index a1d5eb25..2d90a97e 100644 --- a/src/association.h +++ b/src/association.h @@ -29,7 +29,7 @@ class DtlsAssociation : public QObject QSslConfiguration configuration = QSslConfiguration::defaultDtlsConfiguration(); signals: - void handShakeComplited(Packet packetToSend, DtlsAssociation* dtlsAssociation); + void handShakeComplited(); void errorMessage(const QString &message); void warningMessage(const QString &message); void infoMessage(const QString &message); diff --git a/src/dtlsthread.cpp b/src/dtlsthread.cpp index 95b83c2b..c1292509 100644 --- a/src/dtlsthread.cpp +++ b/src/dtlsthread.cpp @@ -17,6 +17,7 @@ Dtlsthread::~Dtlsthread() { void Dtlsthread::run() { + handShakeDone = false; QSettings settings(SETTINGSFILE, QSettings::IniFormat); if (settings.status() != QSettings::NoError) { sendpacket.errorString ="Can't open settings file."; @@ -43,9 +44,19 @@ void Dtlsthread::run() //dtlsAssociation->setKeyCertAndCaCert(cmdComponents[3],cmdComponents[4], cmdComponents[5]); dtlsAssociation->setCipher(cmdComponents[6]); //dtlsAssociation->startHandshake(); - connect(dtlsAssociation, &DtlsAssociation::handShakeComplited,this, &Dtlsthread::writeMassage); + //connect(dtlsAssociation, &DtlsAssociation::handShakeComplited,this, &Dtlsthread::writeMassage); + connect(dtlsAssociation, &DtlsAssociation::handShakeComplited,this, &Dtlsthread::handShakeComplited); + //QEventLoop loop; dtlsAssociation->startHandshake(); + while(!handShakeDone){ + continue; + } + connectStatus("Connected"); + writeMassage(sendpacket, dtlsAssociation); + emit packetSent(sendpacket); + + //dtlsAssociation->socket.waitForReadyRead(); //dtlsAssociation->crypto.resumeHandshake(&(dtlsAssociation->socket)); //loop.exec(); @@ -109,6 +120,11 @@ void Dtlsthread::addServerResponse(const QString &clientAddress, const QByteArra ///emit packetReceived(recPacket); } + +void Dtlsthread::handShakeComplited(){ + handShakeDone = true; +} + void Dtlsthread::writeMassage(Packet packetToSend, DtlsAssociation* dtlsAssociation){ //emit handShakeComplited(packetToSend, this); @@ -117,6 +133,9 @@ void Dtlsthread::writeMassage(Packet packetToSend, DtlsAssociation* dtlsAssociat //emit errorMessage(tr("%1: failed to send a ping - %2").arg(name, crypto.dtlsErrorString())); return; } + dtlsAssociation->socket.waitForReadyRead(); + //addServerResponse() } + diff --git a/src/dtlsthread.h b/src/dtlsthread.h index 77a059da..e3a574da 100644 --- a/src/dtlsthread.h +++ b/src/dtlsthread.h @@ -15,15 +15,19 @@ class Dtlsthread : public QThread Dtlsthread(Packet sendPacket, QObject *parent); virtual ~Dtlsthread(); std::vector dtlsAssociations; + bool handShakeDone; Packet sendpacket; void run() override; // Pure virtual function making this class abstract void sendPersistant(); std::vector getCmdInput(Packet sendpacket, QSettings& settings); -public slots: void writeMassage(Packet packetToSend, DtlsAssociation* dtlsAssociation); +public slots: + void handShakeComplited(); void addServerResponse(const QString &clientInfo, const QByteArray &datagram, const QByteArray &plainText, QHostAddress serverAddress, quint16 serverPort, quint16 userPort); - +signals: + void connectStatus(QString); + void packetSent(Packet); diff --git a/src/packetnetwork.cpp b/src/packetnetwork.cpp index 31a0c503..3f4f4798 100755 --- a/src/packetnetwork.cpp +++ b/src/packetnetwork.cpp @@ -921,9 +921,15 @@ void PacketNetwork::packetToSend(Packet sendpacket) QDEBUG() /*<< ": thread Connection attempt " << connect(pcWindow, SIGNAL(persistentPacketSend(Packet)), thread, SLOT(sendPersistant(Packet)));*/ + //connects from tcp thread/////////////////// << connect(pcWindow, SIGNAL(closeConnection()), thread, SLOT(closeConnection())) << connect(thread, SIGNAL(connectStatus(QString)), pcWindow, SLOT(statusReceiver(QString))) << connect(thread, SIGNAL(packetSent(Packet)), pcWindow, SLOT(packetSentSlot(Packet))); + //connects from packetNetwork in isDtls condition + QDEBUG() << connect(thread, SIGNAL(packetReceived(Packet)), this, SLOT(packetReceivedECHO(Packet))) + << connect(thread, SIGNAL(toStatusBar(QString, int, bool)), this, SLOT(toStatusBarECHO(QString, int, bool))) + << connect(thread, SIGNAL(packetSent(Packet)), this, SLOT(packetSentECHO(Packet))); + QDEBUG() << connect(thread, SIGNAL(destroyed()), this, SLOT(disconnected())); // QDEBUG() << connect(thread, SIGNAL(packetReceived(Packet)), this, SLOT(packetReceivedECHO(Packet))) diff --git a/src/persistentconnection.cpp b/src/persistentconnection.cpp index 167b6c96..3e20398d 100755 --- a/src/persistentconnection.cpp +++ b/src/persistentconnection.cpp @@ -172,6 +172,9 @@ void PersistentConnection::init() if (sendPacket.isSSL()) { tcpOrSSL = "SSL"; } + if(sendPacket.isDTLS()){ + tcpOrSSL = "DTLS"; + } setWindowTitle(tcpOrSSL + "://" + sendPacket.toIP + ":" + QString::number(sendPacket.port)); @@ -222,8 +225,14 @@ void PersistentConnection::refreshTimerTimeout() if (thread->isRunning() && !thread->closeRequest) { QString winTitle = windowTitle(); if (winTitle.startsWith("TCP://") && thread->isEncrypted()) { - winTitle.replace("TCP://", "SSL://"); - setWindowTitle(winTitle); + if(sendPacket.isDTLS()){ + winTitle.replace("TCP://", "DTLS://"); + setWindowTitle(winTitle); + }else{ + winTitle.replace("TCP://", "SSL://"); + setWindowTitle(winTitle); + } + } } From ca1a3c5a03f162b1c6c0e1b81e58ab9849b66650 Mon Sep 17 00:00:00 2001 From: israel Date: Wed, 29 Nov 2023 00:56:26 +0200 Subject: [PATCH 35/79] the client present multiple time the packet he recieved as a respond to the packet he sent --- src/dtlsthread.cpp | 196 ++++++++++++++++++++++++++++++++++++++++++--- src/dtlsthread.h | 5 ++ 2 files changed, 189 insertions(+), 12 deletions(-) diff --git a/src/dtlsthread.cpp b/src/dtlsthread.cpp index c1292509..299d0c4a 100644 --- a/src/dtlsthread.cpp +++ b/src/dtlsthread.cpp @@ -33,27 +33,30 @@ void Dtlsthread::run() quint16 port = cmdComponents[2].toUShort(); //+QString::number(sendpacket.fromPort); //QString test = QString::number(sendpacket.fromPort); - DtlsAssociation *dtlsAssociation = new DtlsAssociation(ipAddressHost, port, sendpacket.fromIP, sendpacket); + DtlsAssociation *dtlsAssociationP = new DtlsAssociation(ipAddressHost, port, sendpacket.fromIP, sendpacket); //dtlsAssociation->setProtocol(QSsl::DtlsV1_2); - dtlsAssociation->newMassageToSend = true; - dtlsAssociation->massageToSend = cmdComponents[0]; - dtlsAssociation->socket; - sendpacket.fromPort = dtlsAssociation->socket.localPort(); - connect(dtlsAssociation, &DtlsAssociation::serverResponse, this, &Dtlsthread::addServerResponse); + dtlsAssociationP->newMassageToSend = true; + dtlsAssociationP->massageToSend = cmdComponents[0]; + dtlsAssociationP->socket; + sendpacket.fromPort = dtlsAssociationP->socket.localPort(); + connect(dtlsAssociationP, &DtlsAssociation::serverResponse, this, &Dtlsthread::addServerResponse); //dtlsAssociation->setKeyCertAndCaCert(cmdComponents[3],cmdComponents[4], cmdComponents[5]); - dtlsAssociation->setCipher(cmdComponents[6]); + dtlsAssociationP->setCipher(cmdComponents[6]); + dtlsAssociation = dtlsAssociationP; //dtlsAssociation->startHandshake(); //connect(dtlsAssociation, &DtlsAssociation::handShakeComplited,this, &Dtlsthread::writeMassage); connect(dtlsAssociation, &DtlsAssociation::handShakeComplited,this, &Dtlsthread::handShakeComplited); //QEventLoop loop; dtlsAssociation->startHandshake(); - while(!handShakeDone){ - continue; - } - connectStatus("Connected"); +// while(!handShakeDone){ +// continue; +// } writeMassage(sendpacket, dtlsAssociation); + + persistentConnectionLoop(); + connectStatus("Connected"); emit packetSent(sendpacket); //dtlsAssociation->socket.waitForReadyRead(); @@ -117,7 +120,7 @@ void Dtlsthread::addServerResponse(const QString &clientAddress, const QByteArra recPacket.errorString = "none"; recPacket.tcpOrUdp = "DTLS"; - ///emit packetReceived(recPacket); + //emit packetReceived(recPacket); } @@ -133,9 +136,178 @@ void Dtlsthread::writeMassage(Packet packetToSend, DtlsAssociation* dtlsAssociat //emit errorMessage(tr("%1: failed to send a ping - %2").arg(name, crypto.dtlsErrorString())); return; } + emit packetSent(packetToSend); dtlsAssociation->socket.waitForReadyRead(); //addServerResponse() } +void Dtlsthread::persistentConnectionLoop() +{ + QUdpSocket* clientConnection = &(dtlsAssociation->socket); + QDEBUG() << "Entering the forever loop"; + int ipMode = 4; + QHostAddress theAddress(sendpacket.toIP); + if (QAbstractSocket::IPv6Protocol == theAddress.protocol()) { + ipMode = 6; + } + + int count = 0; + while (clientConnection->state() == QAbstractSocket::ConnectedState) { + insidePersistent = true; + + + if (sendpacket.hexString.isEmpty() /*&& sendpacket.persistent */ && (clientConnection->bytesAvailable() == 0)) { + count++; + if (count % 10 == 0) { + //QDEBUG() << "Loop and wait." << count++ << clientConnection->state(); + emit connectStatus("Connected and idle."); + } + clientConnection->waitForReadyRead(200); + continue; + } + + if (clientConnection->state() != QAbstractSocket::ConnectedState /*&& sendPacket.persistent*/) { + QDEBUG() << "Connection broken."; + emit connectStatus("Connection broken"); + + break; + } + + if (sendpacket.receiveBeforeSend) { + QDEBUG() << "Wait for data before sending..."; + emit connectStatus("Waiting for data"); + clientConnection->waitForReadyRead(500); + + Packet tcpRCVPacket; + tcpRCVPacket.hexString = Packet::byteArrayToHex(clientConnection->readAll()); + if (!tcpRCVPacket.hexString.trimmed().isEmpty()) { + QDEBUG() << "Received: " << tcpRCVPacket.hexString; + emit connectStatus("Received " + QString::number((tcpRCVPacket.hexString.size() / 3) + 1)); + + tcpRCVPacket.timestamp = QDateTime::currentDateTime(); + tcpRCVPacket.name = QDateTime::currentDateTime().toString(DATETIMEFORMAT); + tcpRCVPacket.tcpOrUdp = "DTLS"; + + if (ipMode < 6) { + tcpRCVPacket.fromIP = Packet::removeIPv6Mapping(clientConnection->peerAddress()); + } else { + tcpRCVPacket.fromIP = (clientConnection->peerAddress()).toString(); + } + + + QDEBUGVAR(tcpRCVPacket.fromIP); + tcpRCVPacket.toIP = "You"; + tcpRCVPacket.port = sendpacket.fromPort; + tcpRCVPacket.fromPort = clientConnection->peerPort(); + if (tcpRCVPacket.hexString.size() > 0) { + emit packetSent(tcpRCVPacket); + + // Do I need to reply? + writeMassage(tcpRCVPacket, dtlsAssociation); + + } + + } else { + QDEBUG() << "No pre-emptive receive data"; + } + + } // end receive before send + + + //sendPacket.fromPort = clientConnection->localPort(); + if(sendpacket.getByteArray().size() > 0) { + emit connectStatus("Sending data:" + sendpacket.asciiString()); + QDEBUG() << "Attempting write data"; + //clientConnection->write(sendpacket.getByteArray()); + //emit packetSent(sendpacket); + } + + Packet tcpPacket; + tcpPacket.timestamp = QDateTime::currentDateTime(); + tcpPacket.name = QDateTime::currentDateTime().toString(DATETIMEFORMAT); + tcpPacket.tcpOrUdp = "TCP"; + if (handShakeDone) { + tcpPacket.tcpOrUdp = "DTLS"; + } + + if (ipMode < 6) { + tcpPacket.fromIP = Packet::removeIPv6Mapping(clientConnection->peerAddress()); + + } else { + tcpPacket.fromIP = (clientConnection->peerAddress()).toString(); + + } + QDEBUGVAR(tcpPacket.fromIP); + + tcpPacket.toIP = "You"; + tcpPacket.port = sendpacket.fromPort; + tcpPacket.fromPort = clientConnection->peerPort(); + + clientConnection->waitForReadyRead(500); + emit connectStatus("Waiting to receive"); + tcpPacket.hexString.clear(); + + while (clientConnection->bytesAvailable()) { + tcpPacket.hexString.append(" "); + tcpPacket.hexString.append(Packet::byteArrayToHex(clientConnection->readAll())); + tcpPacket.hexString = tcpPacket.hexString.simplified(); + clientConnection->waitForReadyRead(100); + } + + + // if (!sendpacket.persistent) { + // emit connectStatus("Disconnecting"); + // clientConnection->disconnectFromHost(); + // } + + QDEBUG() << "packetSent " << tcpPacket.name << tcpPacket.hexString.size(); + + if (sendpacket.receiveBeforeSend) { + if (!tcpPacket.hexString.isEmpty()) { + emit packetSent(tcpPacket); + } + } else { + emit packetSent(tcpPacket); + } + + // Do I need to reply? + //writeResponse(clientConnection, tcpPacket); + //writeMassage(tcpPacket, dtlsAssociation); + + + + emit connectStatus("Reading response"); + tcpPacket.hexString = clientConnection->readAll(); + + tcpPacket.timestamp = QDateTime::currentDateTime(); + tcpPacket.name = QDateTime::currentDateTime().toString(DATETIMEFORMAT); + + + if (tcpPacket.hexString.size() > 0) { + emit packetSent(tcpPacket); + + // Do I need to reply? + writeMassage(tcpPacket, dtlsAssociation); + + } + + + + // if (!sendPacket.persistent) { + // break; + // } else { + // sendPacket.clear(); + // sendPacket.persistent = true; + // QDEBUG() << "Persistent connection. Loop and wait."; + // continue; + // } + } // end while connected + + // if (closeRequest) { + // clientConnection->close(); + // clientConnection->waitForDisconnected(100); + // } + +} diff --git a/src/dtlsthread.h b/src/dtlsthread.h index e3a574da..a17a89cd 100644 --- a/src/dtlsthread.h +++ b/src/dtlsthread.h @@ -16,6 +16,11 @@ class Dtlsthread : public QThread virtual ~Dtlsthread(); std::vector dtlsAssociations; bool handShakeDone; + void persistentConnectionLoop(); + DtlsAssociation* dtlsAssociation; + bool insidePersistent; + + Packet sendpacket; void run() override; // Pure virtual function making this class abstract From e2bb9b538b789ec12aad5b94d5cb60c90cd1bd9d Mon Sep 17 00:00:00 2001 From: israel Date: Wed, 29 Nov 2023 12:57:41 +0200 Subject: [PATCH 36/79] traffic log shows also recived massage, using the dtlsthread. need to implement the resend button --- src/association.cpp | 3 ++- src/association.h | 1 + src/dtlsthread.cpp | 57 ++++++++++++++++++++++++++++++++++++++----- src/dtlsthread.h | 8 ++++-- src/packetnetwork.cpp | 5 ++-- 5 files changed, 62 insertions(+), 12 deletions(-) diff --git a/src/association.cpp b/src/association.cpp index 2a652271..1987f514 100644 --- a/src/association.cpp +++ b/src/association.cpp @@ -130,7 +130,8 @@ void DtlsAssociation::readyRead() if (crypto.isConnectionEncrypted()) { const QByteArray plainText = crypto.decryptDatagram(&socket, dgram); if (plainText.size()) { - emit serverResponse(name, dgram, plainText, crypto.peerAddress(), crypto.peerPort(), socket.localPort()); + //emit serverResponse(name, dgram, plainText, crypto.peerAddress(), crypto.peerPort(), socket.localPort()); + emit receivedDatagram(plainText); return; } diff --git a/src/association.h b/src/association.h index 2d90a97e..e819b77a 100644 --- a/src/association.h +++ b/src/association.h @@ -35,6 +35,7 @@ class DtlsAssociation : public QObject void infoMessage(const QString &message); void serverResponse(const QString &clientInfo, const QByteArray &datagraam, const QByteArray &plainText, QHostAddress peerAddress, quint16 peerPort, quint16 clientPort); + void receivedDatagram(QByteArray plainText); private slots: void udpSocketConnected(); diff --git a/src/dtlsthread.cpp b/src/dtlsthread.cpp index 299d0c4a..15dd78ba 100644 --- a/src/dtlsthread.cpp +++ b/src/dtlsthread.cpp @@ -40,7 +40,11 @@ void Dtlsthread::run() dtlsAssociationP->massageToSend = cmdComponents[0]; dtlsAssociationP->socket; sendpacket.fromPort = dtlsAssociationP->socket.localPort(); - connect(dtlsAssociationP, &DtlsAssociation::serverResponse, this, &Dtlsthread::addServerResponse); + //connect(dtlsAssociationP, &DtlsAssociation::serverResponse, this, &Dtlsthread::addServerResponse); + //connect(this, &Dtlsthread::serverResponse, this, &Dtlsthread::addServerResponse); + connect(dtlsAssociationP, &DtlsAssociation::receivedDatagram, this, &Dtlsthread::receivedDatagram); + PacketNetwork *parentNetwork = qobject_cast(parent()); + connect(this, SIGNAL(packetReceived(Packet)), parentNetwork, SLOT(toTrafficLog(Packet))); //dtlsAssociation->setKeyCertAndCaCert(cmdComponents[3],cmdComponents[4], cmdComponents[5]); dtlsAssociationP->setCipher(cmdComponents[6]); dtlsAssociation = dtlsAssociationP; @@ -56,6 +60,7 @@ void Dtlsthread::run() writeMassage(sendpacket, dtlsAssociation); persistentConnectionLoop(); + connectStatus("Connected"); emit packetSent(sendpacket); @@ -269,7 +274,7 @@ void Dtlsthread::persistentConnectionLoop() emit packetSent(tcpPacket); } } else { - emit packetSent(tcpPacket); + //emit packetSent(tcpPacket); } // Do I need to reply? @@ -279,18 +284,25 @@ void Dtlsthread::persistentConnectionLoop() emit connectStatus("Reading response"); - tcpPacket.hexString = clientConnection->readAll(); + //tcpPacket.hexString = clientConnection->readAll(); + tcpPacket.hexString = recievedMassage; tcpPacket.timestamp = QDateTime::currentDateTime(); tcpPacket.name = QDateTime::currentDateTime().toString(DATETIMEFORMAT); if (tcpPacket.hexString.size() > 0) { - emit packetSent(tcpPacket); - // Do I need to reply? - writeMassage(tcpPacket, dtlsAssociation); + //emit packetSent(tcpPacket); + // Do I need to reply? + //writeMassage(tcpPacket, dtlsAssociation); + //here we find out if there is new massage from server + emit packetReceived(tcpPacket); + //emit connectStatus("last sent massage: " + recievedMassage); + tcpPacket.hexString = ""; + recievedMassage = ""; + sendpacket.hexString = ""; } @@ -311,3 +323,36 @@ void Dtlsthread::persistentConnectionLoop() // } } +void Dtlsthread::receivedDatagram(QByteArray plainText){ + recievedMassage = QString::fromUtf8(plainText); +} + + +void Dtlsthread::sendPersistant(Packet sendpacket) +{ + QUdpSocket* clientConnection = &(dtlsAssociation->socket); + + if ((!sendpacket.hexString.isEmpty()) && (clientConnection->state() == QAbstractSocket::ConnectedState)) { + QDEBUGVAR(sendpacket.hexString); + + writeMassage(sendpacket,dtlsAssociation); + + sendpacket.fromIP = "You"; + + QSettings settings(SETTINGSFILE, QSettings::IniFormat); + int ipMode = settings.value("ipMode", 4).toInt(); + + + if (ipMode < 6) { + sendpacket.toIP = Packet::removeIPv6Mapping(clientConnection->peerAddress()); + } else { + sendpacket.toIP = (clientConnection->peerAddress()).toString(); + } + + sendpacket.port = clientConnection->peerPort(); + sendpacket.fromPort = clientConnection->localPort(); + sendpacket.tcpOrUdp = "DTLS"; + + emit packetSent(sendpacket); + } +} diff --git a/src/dtlsthread.h b/src/dtlsthread.h index a17a89cd..af529c91 100644 --- a/src/dtlsthread.h +++ b/src/dtlsthread.h @@ -19,21 +19,25 @@ class Dtlsthread : public QThread void persistentConnectionLoop(); DtlsAssociation* dtlsAssociation; bool insidePersistent; + QString recievedMassage; Packet sendpacket; void run() override; // Pure virtual function making this class abstract - void sendPersistant(); + //void sendPersistant(); + void sendPersistant(Packet sendpacket); std::vector getCmdInput(Packet sendpacket, QSettings& settings); void writeMassage(Packet packetToSend, DtlsAssociation* dtlsAssociation); public slots: void handShakeComplited(); + void receivedDatagram(QByteArray plainText); void addServerResponse(const QString &clientInfo, const QByteArray &datagram, const QByteArray &plainText, QHostAddress serverAddress, quint16 serverPort, quint16 userPort); signals: void connectStatus(QString); void packetSent(Packet); - + //void serverResponse(const QString &clientInfo, const QByteArray &datagraam, const QByteArray &plainText, QHostAddress peerAddress, quint16 peerPort, quint16 clientPort); + void packetReceived(Packet); }; diff --git a/src/packetnetwork.cpp b/src/packetnetwork.cpp index 3f4f4798..61459cfe 100755 --- a/src/packetnetwork.cpp +++ b/src/packetnetwork.cpp @@ -919,9 +919,8 @@ void PacketNetwork::packetToSend(Packet sendpacket) pcWindow->dthread = thread; - QDEBUG() /*<< ": thread Connection attempt " << - connect(pcWindow, SIGNAL(persistentPacketSend(Packet)), thread, SLOT(sendPersistant(Packet)));*/ - //connects from tcp thread/////////////////// + QDEBUG() << ": thread Connection attempt " + << connect(pcWindow, SIGNAL(persistentPacketSend(Packet)), thread, SLOT(sendPersistant(Packet))) << connect(pcWindow, SIGNAL(closeConnection()), thread, SLOT(closeConnection())) << connect(thread, SIGNAL(connectStatus(QString)), pcWindow, SLOT(statusReceiver(QString))) << connect(thread, SIGNAL(packetSent(Packet)), pcWindow, SLOT(packetSentSlot(Packet))); From 5bf4ba15aa574d3b73f1b1ea2f47e9a14b2b685d Mon Sep 17 00:00:00 2001 From: israel Date: Wed, 29 Nov 2023 16:22:39 +0200 Subject: [PATCH 37/79] the client working on persistent mode, changes in the traffic log need to be treaten --- src/dtlsthread.cpp | 13 +++++++------ src/dtlsthread.h | 4 ++-- src/persistentconnection.cpp | 3 ++- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/dtlsthread.cpp b/src/dtlsthread.cpp index 15dd78ba..e666114e 100644 --- a/src/dtlsthread.cpp +++ b/src/dtlsthread.cpp @@ -37,14 +37,15 @@ void Dtlsthread::run() //dtlsAssociation->setProtocol(QSsl::DtlsV1_2); dtlsAssociationP->newMassageToSend = true; - dtlsAssociationP->massageToSend = cmdComponents[0]; + //dtlsAssociationP->massageToSend = cmdComponents[0]; dtlsAssociationP->socket; sendpacket.fromPort = dtlsAssociationP->socket.localPort(); //connect(dtlsAssociationP, &DtlsAssociation::serverResponse, this, &Dtlsthread::addServerResponse); - //connect(this, &Dtlsthread::serverResponse, this, &Dtlsthread::addServerResponse); + connect(this, &Dtlsthread::serverResponse, this, &Dtlsthread::addServerResponse); connect(dtlsAssociationP, &DtlsAssociation::receivedDatagram, this, &Dtlsthread::receivedDatagram); PacketNetwork *parentNetwork = qobject_cast(parent()); connect(this, SIGNAL(packetReceived(Packet)), parentNetwork, SLOT(toTrafficLog(Packet))); + //connect(this, SIGNAL(packetReceived(Packet)), this, SLOT(addServerResponse(Packet))); //dtlsAssociation->setKeyCertAndCaCert(cmdComponents[3],cmdComponents[4], cmdComponents[5]); dtlsAssociationP->setCipher(cmdComponents[6]); dtlsAssociation = dtlsAssociationP; @@ -62,7 +63,7 @@ void Dtlsthread::run() persistentConnectionLoop(); connectStatus("Connected"); - emit packetSent(sendpacket); + //emit packetSent(sendpacket); //dtlsAssociation->socket.waitForReadyRead(); //dtlsAssociation->crypto.resumeHandshake(&(dtlsAssociation->socket)); @@ -136,13 +137,13 @@ void Dtlsthread::handShakeComplited(){ void Dtlsthread::writeMassage(Packet packetToSend, DtlsAssociation* dtlsAssociation){ //emit handShakeComplited(packetToSend, this); - const qint64 written = dtlsAssociation->crypto.writeDatagramEncrypted(&(dtlsAssociation->socket), dtlsAssociation->massageToSend.toLatin1()); + const qint64 written = dtlsAssociation->crypto.writeDatagramEncrypted(&(dtlsAssociation->socket), packetToSend.asciiString().toLatin1()); if (written <= 0) { //emit errorMessage(tr("%1: failed to send a ping - %2").arg(name, crypto.dtlsErrorString())); return; } emit packetSent(packetToSend); - dtlsAssociation->socket.waitForReadyRead(); + //dtlsAssociation->socket.waitForReadyRead(); //addServerResponse() } @@ -353,6 +354,6 @@ void Dtlsthread::sendPersistant(Packet sendpacket) sendpacket.fromPort = clientConnection->localPort(); sendpacket.tcpOrUdp = "DTLS"; - emit packetSent(sendpacket); + //emit packetSent(sendpacket); } } diff --git a/src/dtlsthread.h b/src/dtlsthread.h index af529c91..13ed1776 100644 --- a/src/dtlsthread.h +++ b/src/dtlsthread.h @@ -26,17 +26,17 @@ class Dtlsthread : public QThread Packet sendpacket; void run() override; // Pure virtual function making this class abstract //void sendPersistant(); - void sendPersistant(Packet sendpacket); std::vector getCmdInput(Packet sendpacket, QSettings& settings); void writeMassage(Packet packetToSend, DtlsAssociation* dtlsAssociation); public slots: + void sendPersistant(Packet sendpacket); void handShakeComplited(); void receivedDatagram(QByteArray plainText); void addServerResponse(const QString &clientInfo, const QByteArray &datagram, const QByteArray &plainText, QHostAddress serverAddress, quint16 serverPort, quint16 userPort); signals: void connectStatus(QString); void packetSent(Packet); - //void serverResponse(const QString &clientInfo, const QByteArray &datagraam, const QByteArray &plainText, QHostAddress peerAddress, quint16 peerPort, quint16 clientPort); + void serverResponse(const QString &clientInfo, const QByteArray &datagraam, const QByteArray &plainText, QHostAddress peerAddress, quint16 peerPort, quint16 clientPort); void packetReceived(Packet); diff --git a/src/persistentconnection.cpp b/src/persistentconnection.cpp index 3e20398d..db8270f3 100755 --- a/src/persistentconnection.cpp +++ b/src/persistentconnection.cpp @@ -267,7 +267,6 @@ void PersistentConnection::refreshTimerTimeout() - // QDEBUG() <<"Diff:" << diff; @@ -352,6 +351,8 @@ void PersistentConnection::socketDisconnected() statusReceiver("not connected"); } +//connect(pcWindow, SIGNAL(persistentPacketSend(Packet)), thread, SLOT(sendPersistant(Packet))) + void PersistentConnection::on_asciiSendButton_clicked() { QString ascii = ui->asciiLineEdit->text(); From 5d33908bbe4f09a1f84032d9bb449df212593bfc Mon Sep 17 00:00:00 2001 From: israel Date: Thu, 30 Nov 2023 11:29:05 +0200 Subject: [PATCH 38/79] persistent simple sending functionality is working using thread --- src/dtlsthread.cpp | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/dtlsthread.cpp b/src/dtlsthread.cpp index e666114e..12ba07b3 100644 --- a/src/dtlsthread.cpp +++ b/src/dtlsthread.cpp @@ -143,6 +143,7 @@ void Dtlsthread::writeMassage(Packet packetToSend, DtlsAssociation* dtlsAssociat return; } emit packetSent(packetToSend); + //packetToSend.hexString = ""; //dtlsAssociation->socket.waitForReadyRead(); //addServerResponse() } @@ -226,6 +227,7 @@ void Dtlsthread::persistentConnectionLoop() if(sendpacket.getByteArray().size() > 0) { emit connectStatus("Sending data:" + sendpacket.asciiString()); QDEBUG() << "Attempting write data"; + //writeMassage(sendpacket, dtlsAssociation); //clientConnection->write(sendpacket.getByteArray()); //emit packetSent(sendpacket); } @@ -233,10 +235,8 @@ void Dtlsthread::persistentConnectionLoop() Packet tcpPacket; tcpPacket.timestamp = QDateTime::currentDateTime(); tcpPacket.name = QDateTime::currentDateTime().toString(DATETIMEFORMAT); - tcpPacket.tcpOrUdp = "TCP"; - if (handShakeDone) { - tcpPacket.tcpOrUdp = "DTLS"; - } + tcpPacket.tcpOrUdp = "DTLS"; + if (ipMode < 6) { tcpPacket.fromIP = Packet::removeIPv6Mapping(clientConnection->peerAddress()); @@ -248,8 +248,8 @@ void Dtlsthread::persistentConnectionLoop() QDEBUGVAR(tcpPacket.fromIP); tcpPacket.toIP = "You"; - tcpPacket.port = sendpacket.fromPort; - tcpPacket.fromPort = clientConnection->peerPort(); + tcpPacket.port = dtlsAssociation->socket.localPort(); + tcpPacket.fromPort = sendpacket.port;/////////////////////fromport clientConnection->waitForReadyRead(500); emit connectStatus("Waiting to receive"); @@ -299,7 +299,7 @@ void Dtlsthread::persistentConnectionLoop() // Do I need to reply? //writeMassage(tcpPacket, dtlsAssociation); //here we find out if there is new massage from server - emit packetReceived(tcpPacket); + //emit packetReceived(tcpPacket); //emit connectStatus("last sent massage: " + recievedMassage); tcpPacket.hexString = ""; recievedMassage = ""; @@ -326,6 +326,18 @@ void Dtlsthread::persistentConnectionLoop() } void Dtlsthread::receivedDatagram(QByteArray plainText){ recievedMassage = QString::fromUtf8(plainText); + Packet recPacket; + recPacket.init(); + recPacket.fromIP = dtlsAssociation->crypto.peerAddress().toString(); + recPacket.fromPort = dtlsAssociation->crypto.peerPort(); + QString massageFromTheOtherPeer = QString::fromUtf8(plainText); + recPacket.hexString = massageFromTheOtherPeer; + recPacket.toIP = dtlsAssociation->socket.localAddress().toString(); + recPacket.port = dtlsAssociation->socket.localPort(); + recPacket.errorString = "none"; + recPacket.tcpOrUdp = "DTLS"; + + emit packetReceived(recPacket); } @@ -335,7 +347,7 @@ void Dtlsthread::sendPersistant(Packet sendpacket) if ((!sendpacket.hexString.isEmpty()) && (clientConnection->state() == QAbstractSocket::ConnectedState)) { QDEBUGVAR(sendpacket.hexString); - + sendpacket.fromPort = clientConnection->localPort(); writeMassage(sendpacket,dtlsAssociation); sendpacket.fromIP = "You"; From a0b2a664e6cd96bfff20f95417c8b1a19734b6b7 Mon Sep 17 00:00:00 2001 From: israel Date: Sun, 3 Dec 2023 11:30:24 +0200 Subject: [PATCH 39/79] non-persistent sending using the closeRequest flag to end the while of persistent connection inside dtlsTread class --- src/dtlsthread.cpp | 19 ++++++++++++++----- src/dtlsthread.h | 2 ++ src/packetnetwork.cpp | 4 ++++ 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/dtlsthread.cpp b/src/dtlsthread.cpp index 12ba07b3..1a0050d4 100644 --- a/src/dtlsthread.cpp +++ b/src/dtlsthread.cpp @@ -17,6 +17,8 @@ Dtlsthread::~Dtlsthread() { void Dtlsthread::run() { + closeRequest = false; + handShakeDone = false; QSettings settings(SETTINGSFILE, QSettings::IniFormat); if (settings.status() != QSettings::NoError) { @@ -161,7 +163,7 @@ void Dtlsthread::persistentConnectionLoop() } int count = 0; - while (clientConnection->state() == QAbstractSocket::ConnectedState) { + while (clientConnection->state() == QAbstractSocket::ConnectedState && !closeRequest) { insidePersistent = true; @@ -318,10 +320,10 @@ void Dtlsthread::persistentConnectionLoop() // } } // end while connected - // if (closeRequest) { - // clientConnection->close(); - // clientConnection->waitForDisconnected(100); - // } + if (closeRequest) { + clientConnection->close(); + clientConnection->waitForDisconnected(100); + } } void Dtlsthread::receivedDatagram(QByteArray plainText){ @@ -369,3 +371,10 @@ void Dtlsthread::sendPersistant(Packet sendpacket) //emit packetSent(sendpacket); } } + +void Dtlsthread::onTimeout(){ +// dtlsAssociation->socket.disconnectFromHost(); +// dtlsAssociation->socket.close(); +// //this->terminate(); + closeRequest = true; +} diff --git a/src/dtlsthread.h b/src/dtlsthread.h index 13ed1776..cf741ad5 100644 --- a/src/dtlsthread.h +++ b/src/dtlsthread.h @@ -12,6 +12,7 @@ class Dtlsthread : public QThread public: + bool closeRequest; Dtlsthread(Packet sendPacket, QObject *parent); virtual ~Dtlsthread(); std::vector dtlsAssociations; @@ -29,6 +30,7 @@ class Dtlsthread : public QThread std::vector getCmdInput(Packet sendpacket, QSettings& settings); void writeMassage(Packet packetToSend, DtlsAssociation* dtlsAssociation); public slots: + void onTimeout(); void sendPersistant(Packet sendpacket); void handShakeComplited(); void receivedDatagram(QByteArray plainText); diff --git a/src/packetnetwork.cpp b/src/packetnetwork.cpp index 61459cfe..4859185e 100755 --- a/src/packetnetwork.cpp +++ b/src/packetnetwork.cpp @@ -946,6 +946,9 @@ void PacketNetwork::packetToSend(Packet sendpacket) //Network manager will manage this thread so the UI window doesn't need to. dtlsthreadList.append(thread); + QTimer* timer = new QTimer(this); + connect(timer, SIGNAL(timeout()), thread, SLOT(onTimeout())); + timer->start(1000); } @@ -1275,3 +1278,4 @@ void PacketNetwork::addServerResponse(const QString &clientAddress, const QByteA emit packetReceived(recPacket); } + From 13aad45c7b21e424d6c133ad8a2222fa9796ef1c Mon Sep 17 00:00:00 2001 From: israel Date: Sun, 3 Dec 2023 12:07:42 +0200 Subject: [PATCH 40/79] added to dtlsthread a field of Qtimer to end it after the time out --- src/dtlsthread.cpp | 1 + src/dtlsthread.h | 2 ++ src/packetnetwork.cpp | 2 ++ 3 files changed, 5 insertions(+) diff --git a/src/dtlsthread.cpp b/src/dtlsthread.cpp index 1a0050d4..ddb507b1 100644 --- a/src/dtlsthread.cpp +++ b/src/dtlsthread.cpp @@ -377,4 +377,5 @@ void Dtlsthread::onTimeout(){ // dtlsAssociation->socket.close(); // //this->terminate(); closeRequest = true; + timer->stop(); } diff --git a/src/dtlsthread.h b/src/dtlsthread.h index cf741ad5..ccc1c5ad 100644 --- a/src/dtlsthread.h +++ b/src/dtlsthread.h @@ -5,6 +5,7 @@ #include "packet.h" #include "QSettings" #include "association.h" +//#include "QTimer" class Dtlsthread : public QThread { @@ -12,6 +13,7 @@ class Dtlsthread : public QThread public: + QTimer* timer; bool closeRequest; Dtlsthread(Packet sendPacket, QObject *parent); virtual ~Dtlsthread(); diff --git a/src/packetnetwork.cpp b/src/packetnetwork.cpp index 4859185e..fe49bfc1 100755 --- a/src/packetnetwork.cpp +++ b/src/packetnetwork.cpp @@ -946,7 +946,9 @@ void PacketNetwork::packetToSend(Packet sendpacket) //Network manager will manage this thread so the UI window doesn't need to. dtlsthreadList.append(thread); + QTimer* timer = new QTimer(this); + thread->timer = timer; connect(timer, SIGNAL(timeout()), thread, SLOT(onTimeout())); timer->start(1000); From 75547a67923c9ae1854926b571c7ea8d3a15af1c Mon Sep 17 00:00:00 2001 From: israel Date: Sun, 3 Dec 2023 12:16:39 +0200 Subject: [PATCH 41/79] also persistent and non-persistent is working on the client PS --- src/packetnetwork.cpp | 89 +++++++++++++++++++++++++++++-------------- 1 file changed, 61 insertions(+), 28 deletions(-) diff --git a/src/packetnetwork.cpp b/src/packetnetwork.cpp index fe49bfc1..d40ee803 100755 --- a/src/packetnetwork.cpp +++ b/src/packetnetwork.cpp @@ -912,45 +912,78 @@ void PacketNetwork::packetToSend(Packet sendpacket) if(sendpacket.isDTLS()){ - PersistentConnection * pcWindow = new PersistentConnection(); - Dtlsthread * thread = new Dtlsthread(sendpacket, this); - pcWindow->sendPacket = sendpacket; - pcWindow->init(); - pcWindow->dthread = thread; + QSettings settings(SETTINGSFILE, QSettings::IniFormat); + if(settings.value("leaveSessionOpen").toString() == "true"){ + PersistentConnection * pcWindow = new PersistentConnection(); + Dtlsthread * thread = new Dtlsthread(sendpacket, this); + pcWindow->sendPacket = sendpacket; + pcWindow->init(); + pcWindow->dthread = thread; - QDEBUG() << ": thread Connection attempt " - << connect(pcWindow, SIGNAL(persistentPacketSend(Packet)), thread, SLOT(sendPersistant(Packet))) - << connect(pcWindow, SIGNAL(closeConnection()), thread, SLOT(closeConnection())) - << connect(thread, SIGNAL(connectStatus(QString)), pcWindow, SLOT(statusReceiver(QString))) - << connect(thread, SIGNAL(packetSent(Packet)), pcWindow, SLOT(packetSentSlot(Packet))); - //connects from packetNetwork in isDtls condition - QDEBUG() << connect(thread, SIGNAL(packetReceived(Packet)), this, SLOT(packetReceivedECHO(Packet))) - << connect(thread, SIGNAL(toStatusBar(QString, int, bool)), this, SLOT(toStatusBarECHO(QString, int, bool))) - << connect(thread, SIGNAL(packetSent(Packet)), this, SLOT(packetSentECHO(Packet))); - QDEBUG() << connect(thread, SIGNAL(destroyed()), this, SLOT(disconnected())); + QDEBUG() << ": thread Connection attempt " + << connect(pcWindow, SIGNAL(persistentPacketSend(Packet)), thread, SLOT(sendPersistant(Packet))) + << connect(pcWindow, SIGNAL(closeConnection()), thread, SLOT(closeConnection())) + << connect(thread, SIGNAL(connectStatus(QString)), pcWindow, SLOT(statusReceiver(QString))) + << connect(thread, SIGNAL(packetSent(Packet)), pcWindow, SLOT(packetSentSlot(Packet))); + //connects from packetNetwork in isDtls condition + QDEBUG() << connect(thread, SIGNAL(packetReceived(Packet)), this, SLOT(packetReceivedECHO(Packet))) + << connect(thread, SIGNAL(toStatusBar(QString, int, bool)), this, SLOT(toStatusBarECHO(QString, int, bool))) + << connect(thread, SIGNAL(packetSent(Packet)), this, SLOT(packetSentECHO(Packet))); + QDEBUG() << connect(thread, SIGNAL(destroyed()), this, SLOT(disconnected())); -// QDEBUG() << connect(thread, SIGNAL(packetReceived(Packet)), this, SLOT(packetReceivedECHO(Packet))) -// << connect(thread, SIGNAL(toStatusBar(QString, int, bool)), this, SLOT(toStatusBarECHO(QString, int, bool))) -// << connect(thread, SIGNAL(packetSent(Packet)), this, SLOT(packetSentECHO(Packet))); + // QDEBUG() << connect(thread, SIGNAL(packetReceived(Packet)), this, SLOT(packetReceivedECHO(Packet))) + // << connect(thread, SIGNAL(toStatusBar(QString, int, bool)), this, SLOT(toStatusBarECHO(QString, int, bool))) + // << connect(thread, SIGNAL(packetSent(Packet)), this, SLOT(packetSentECHO(Packet))); - //connect(&packetNetwork, SIGNAL(packetSent(Packet)), - // this, SLOT(toTrafficLog(Packet))); + //connect(&packetNetwork, SIGNAL(packetSent(Packet)), + // this, SLOT(toTrafficLog(Packet))); - pcWindow->show(); - thread->start(); + pcWindow->show(); + thread->start(); + + } + else{ + // PersistentConnection * pcWindow = new PersistentConnection(); + Dtlsthread * thread = new Dtlsthread(sendpacket, this); + // pcWindow->sendPacket = sendpacket; + // pcWindow->init(); + // pcWindow->dthread = thread; + // QDEBUG() << ": thread Connection attempt " + // << connect(pcWindow, SIGNAL(persistentPacketSend(Packet)), thread, SLOT(sendPersistant(Packet))) + // << connect(pcWindow, SIGNAL(closeConnection()), thread, SLOT(closeConnection())) + // << connect(thread, SIGNAL(connectStatus(QString)), pcWindow, SLOT(statusReceiver(QString))) + // << connect(thread, SIGNAL(packetSent(Packet)), pcWindow, SLOT(packetSentSlot(Packet))); + //connects from packetNetwork in isDtls condition + QDEBUG() << connect(thread, SIGNAL(packetReceived(Packet)), this, SLOT(packetReceivedECHO(Packet))) + << connect(thread, SIGNAL(toStatusBar(QString, int, bool)), this, SLOT(toStatusBarECHO(QString, int, bool))) + << connect(thread, SIGNAL(packetSent(Packet)), this, SLOT(packetSentECHO(Packet))); + QDEBUG() << connect(thread, SIGNAL(destroyed()), this, SLOT(disconnected())); - //Network manager will manage this thread so the UI window doesn't need to. - dtlsthreadList.append(thread); - QTimer* timer = new QTimer(this); - thread->timer = timer; - connect(timer, SIGNAL(timeout()), thread, SLOT(onTimeout())); - timer->start(1000); + // QDEBUG() << connect(thread, SIGNAL(packetReceived(Packet)), this, SLOT(packetReceivedECHO(Packet))) + // << connect(thread, SIGNAL(toStatusBar(QString, int, bool)), this, SLOT(toStatusBarECHO(QString, int, bool))) + // << connect(thread, SIGNAL(packetSent(Packet)), this, SLOT(packetSentECHO(Packet))); + + + //connect(&packetNetwork, SIGNAL(packetSent(Packet)), + // this, SLOT(toTrafficLog(Packet))); + + // pcWindow->show(); + thread->start(); + //Network manager will manage this thread so the UI window doesn't need to. + dtlsthreadList.append(thread); + + QTimer* timer = new QTimer(this); + thread->timer = timer; + connect(timer, SIGNAL(timeout()), thread, SLOT(onTimeout())); + timer->start(1000); + + } } From 33d11f7f4309cb87173dec86f7f9561e7188290a Mon Sep 17 00:00:00 2001 From: israel Date: Sun, 3 Dec 2023 17:05:37 +0200 Subject: [PATCH 42/79] use all the user input with threads --- src/association.cpp | 84 ++++++++++++++++++++++----------------------- src/association.h | 8 ++--- src/dtlsthread.cpp | 26 ++------------ 3 files changed, 47 insertions(+), 71 deletions(-) diff --git a/src/association.cpp b/src/association.cpp index 1987f514..8b3b6b77 100644 --- a/src/association.cpp +++ b/src/association.cpp @@ -5,7 +5,7 @@ #include "packet.h" DtlsAssociation::DtlsAssociation(const QHostAddress &address, quint16 port, - const QString &connectionName, Packet packetToSend) + const QString &connectionName, std::vector cmdComponents) : name(connectionName), crypto(QSslSocket::SslClientMode) { @@ -13,19 +13,19 @@ DtlsAssociation::DtlsAssociation(const QHostAddress &address, quint16 port, //auto configuration = QSslConfiguration::defaultDtlsConfiguration(); ////////////////////// - QFile certFile("C:/Users/israe/OneDrive - ort braude college of engineering/rsa_encryption/client-signed-cert.pem"); + QFile certFile(cmdComponents[4]);//4 if(!certFile.open(QIODevice::ReadOnly)){ return; } QSslCertificate certificate(&certFile, QSsl::Pem); - QFile keyFile("C:/Users/israe/OneDrive - ort braude college of engineering/rsa_encryption/client-key.pem"); + QFile keyFile(cmdComponents[3]);//3 if(!keyFile.open(QIODevice::ReadOnly)){ return; } QSslKey privateKey(&keyFile, QSsl::Rsa); // Or QSsl::Ec if your key is ECDSA - QFile caCertFile("C:/Users/israe/OneDrive - ort braude college of engineering/rsa_encryption/ca-signed-cert/signed-cert.pem"); + QFile caCertFile(cmdComponents[5]);//5 if(!caCertFile.open(QIODevice::ReadOnly)){ return; } @@ -61,8 +61,8 @@ DtlsAssociation::DtlsAssociation(const QHostAddress &address, quint16 port, //! [13] //! [4] - pingTimer.setInterval(5000); - connect(&pingTimer, &QTimer::timeout, this, &DtlsAssociation::pingTimeout); + //pingTimer.setInterval(5000); + //connect(&pingTimer, &QTimer::timeout, this, &DtlsAssociation::pingTimeout); //! [4] } @@ -197,23 +197,23 @@ void DtlsAssociation::pskRequired(QSslPreSharedKeyAuthenticator *auth) //! //! //! only for ping massage -void DtlsAssociation::pingTimeout() -{ - - //static const QString message = QStringLiteral("I am %1, please, accept our ping %2"); - //const qint64 written = crypto.writeDatagramEncrypted(&socket, message.arg(name).arg(ping).toLatin1()); - if(this->newMassageToSend){ - emit handShakeComplited(); - const qint64 written = crypto.writeDatagramEncrypted(&socket, massageToSend.toLatin1()); - if (written <= 0) { - emit errorMessage(tr("%1: failed to send a ping - %2").arg(name, crypto.dtlsErrorString())); - pingTimer.stop(); - return; - } - this->newMassageToSend = false; - ++ping; - } -} +//void DtlsAssociation::pingTimeout() +//{ + +// //static const QString message = QStringLiteral("I am %1, please, accept our ping %2"); +// //const qint64 written = crypto.writeDatagramEncrypted(&socket, message.arg(name).arg(ping).toLatin1()); +// if(this->newMassageToSend){ +// emit handShakeComplited(); +// const qint64 written = crypto.writeDatagramEncrypted(&socket, massageToSend.toLatin1()); +// if (written <= 0) { +// emit errorMessage(tr("%1: failed to send a ping - %2").arg(name, crypto.dtlsErrorString())); +// pingTimer.stop(); +// return; +// } +// this->newMassageToSend = false; +// ++ping; +// } +//} //! [10] @@ -224,25 +224,25 @@ void DtlsAssociation::setCipher(QString chosenCipher) { crypto.setDtlsConfiguration(configuration); } -void DtlsAssociation::setKeyCertAndCaCert(QString keyPath, QString certPath, QString caPath) { - QFile keyFile(keyPath); - QFile certFile(certPath); - QFile caFile(caPath); - if (certFile.open(QIODevice::ReadOnly) && keyFile.open(QIODevice::ReadOnly) && caFile.open(QIODevice::ReadOnly)) { - QSslKey privateKey(&keyFile, QSsl::Rsa, QSsl::Pem); - QSslCertificate certificate(&certFile, QSsl::Pem); - QSslCertificate caCertificate(&caFile, QSsl::Pem); - - configuration.setPrivateKey(privateKey); - configuration.setLocalCertificate(certificate); - configuration.setCaCertificates(QList() << caCertificate); - - crypto.setDtlsConfiguration(configuration); - } - else { - //QDebug("Error loading certs or key"); - } -} +//void DtlsAssociation::setKeyCertAndCaCert(QString keyPath, QString certPath, QString caPath) { +// QFile keyFile(keyPath); +// QFile certFile(certPath); +// QFile caFile(caPath); +// if (certFile.open(QIODevice::ReadOnly) && keyFile.open(QIODevice::ReadOnly) && caFile.open(QIODevice::ReadOnly)) { +// QSslKey privateKey(&keyFile, QSsl::Rsa, QSsl::Pem); +// QSslCertificate certificate(&certFile, QSsl::Pem); +// QSslCertificate caCertificate(&caFile, QSsl::Pem); + +// configuration.setPrivateKey(privateKey); +// configuration.setLocalCertificate(certificate); +// configuration.setCaCertificates(QList() << caCertificate); + +// crypto.setDtlsConfiguration(configuration); +// } +// else { +// //QDebug("Error loading certs or key"); +// } +//} //////////////////////// //QFile certFile("C:/Users/israe/OneDrive - ort braude college of engineering/rsa_encryption/client-signed-cert.pem"); //if(!certFile.open(QIODevice::ReadOnly)){ diff --git a/src/association.h b/src/association.h index e819b77a..ca86a950 100644 --- a/src/association.h +++ b/src/association.h @@ -14,17 +14,15 @@ class DtlsAssociation : public QObject public: QDtls crypto; - bool newMassageToSend = false; - QString massageToSend; QUdpSocket socket; QString name; Packet packetToSend; DtlsAssociation(const QHostAddress &address, quint16 port, - const QString &connectionName, Packet packetToSend); + const QString &connectionName, std::vector cmdComponents); ~DtlsAssociation(); void startHandshake(); - void setKeyCertAndCaCert(QString keyPath, QString certPath, QString caPath); + //void setKeyCertAndCaCert(QString keyPath, QString certPath, QString caPath); void setCipher(QString chosenCipher); QSslConfiguration configuration = QSslConfiguration::defaultDtlsConfiguration(); @@ -42,7 +40,7 @@ private slots: void readyRead(); void handshakeTimeout(); void pskRequired(QSslPreSharedKeyAuthenticator *auth); - void pingTimeout(); + //void pingTimeout(); //void writeMassage(); diff --git a/src/dtlsthread.cpp b/src/dtlsthread.cpp index ddb507b1..96c5d24d 100644 --- a/src/dtlsthread.cpp +++ b/src/dtlsthread.cpp @@ -33,45 +33,23 @@ void Dtlsthread::run() QHostAddress ipAddressHost; ipAddressHost.setAddress(ipAddress); quint16 port = cmdComponents[2].toUShort(); - //+QString::number(sendpacket.fromPort); - //QString test = QString::number(sendpacket.fromPort); - DtlsAssociation *dtlsAssociationP = new DtlsAssociation(ipAddressHost, port, sendpacket.fromIP, sendpacket); - //dtlsAssociation->setProtocol(QSsl::DtlsV1_2); - - dtlsAssociationP->newMassageToSend = true; - //dtlsAssociationP->massageToSend = cmdComponents[0]; + DtlsAssociation *dtlsAssociationP = new DtlsAssociation(ipAddressHost, port, sendpacket.fromIP, cmdComponents); dtlsAssociationP->socket; sendpacket.fromPort = dtlsAssociationP->socket.localPort(); - //connect(dtlsAssociationP, &DtlsAssociation::serverResponse, this, &Dtlsthread::addServerResponse); connect(this, &Dtlsthread::serverResponse, this, &Dtlsthread::addServerResponse); connect(dtlsAssociationP, &DtlsAssociation::receivedDatagram, this, &Dtlsthread::receivedDatagram); PacketNetwork *parentNetwork = qobject_cast(parent()); connect(this, SIGNAL(packetReceived(Packet)), parentNetwork, SLOT(toTrafficLog(Packet))); - //connect(this, SIGNAL(packetReceived(Packet)), this, SLOT(addServerResponse(Packet))); - //dtlsAssociation->setKeyCertAndCaCert(cmdComponents[3],cmdComponents[4], cmdComponents[5]); dtlsAssociationP->setCipher(cmdComponents[6]); dtlsAssociation = dtlsAssociationP; - //dtlsAssociation->startHandshake(); - //connect(dtlsAssociation, &DtlsAssociation::handShakeComplited,this, &Dtlsthread::writeMassage); connect(dtlsAssociation, &DtlsAssociation::handShakeComplited,this, &Dtlsthread::handShakeComplited); - - //QEventLoop loop; dtlsAssociation->startHandshake(); -// while(!handShakeDone){ -// continue; -// } + writeMassage(sendpacket, dtlsAssociation); persistentConnectionLoop(); connectStatus("Connected"); - //emit packetSent(sendpacket); - - //dtlsAssociation->socket.waitForReadyRead(); - //dtlsAssociation->crypto.resumeHandshake(&(dtlsAssociation->socket)); - //loop.exec(); - - //dtlsAssociation->crypto.doHandshake(&(dtlsAssociation->socket)); } From 8d62e0b94c87a19c693be599796e2203e5a674bf Mon Sep 17 00:00:00 2001 From: israel Date: Sun, 3 Dec 2023 19:33:36 +0200 Subject: [PATCH 43/79] the initialize of dtlsAssociation extracted from the run function of dtlsTread class, into initDtlsAssociation func --- src/association.cpp | 50 ++++++++++------------------------------ src/dtlsthread.cpp | 56 ++++++++++++++++++--------------------------- src/dtlsthread.h | 1 + 3 files changed, 35 insertions(+), 72 deletions(-) diff --git a/src/association.cpp b/src/association.cpp index 8b3b6b77..dd7359ae 100644 --- a/src/association.cpp +++ b/src/association.cpp @@ -9,10 +9,7 @@ DtlsAssociation::DtlsAssociation(const QHostAddress &address, quint16 port, : name(connectionName), crypto(QSslSocket::SslClientMode) { - //! [1] - //auto configuration = QSslConfiguration::defaultDtlsConfiguration(); - ////////////////////// QFile certFile(cmdComponents[4]);//4 if(!certFile.open(QIODevice::ReadOnly)){ return; @@ -31,39 +28,22 @@ DtlsAssociation::DtlsAssociation(const QHostAddress &address, quint16 port, } QSslCertificate caCertificate(&caCertFile, QSsl::Pem); - //auto configuration = QSslConfiguration::defaultDtlsConfiguration(); - //configuration.setCiphers("AES256-GCM-SHA384"); - configuration.setLocalCertificate(certificate); configuration.setPrivateKey(privateKey); configuration.setCaCertificates(QList() << caCertificate); - ////////////////////// configuration.setPeerVerifyMode(QSslSocket::VerifyNone); crypto.setPeer(address, port); crypto.setDtlsConfiguration(configuration); - - //! [1] - - //! [2] - //connect(&crypto, &QDtls::handshakeTimeout, this, &DtlsAssociation::handshakeTimeout); - //! [2] connect(&crypto, &QDtls::pskRequired, this, &DtlsAssociation::pskRequired); //! [3] socket.connectToHost(address.toString(), port); socket.waitForConnected(); //! [3] //! [13] - //QEventLoop loop; connect(&socket, &QUdpSocket::readyRead, this, &DtlsAssociation::readyRead); - //loop.exec(); - - //! [13] - //! [4] - //pingTimer.setInterval(5000); - //connect(&pingTimer, &QTimer::timeout, this, &DtlsAssociation::pingTimeout); - //! [4] + } //! [12] @@ -86,14 +66,12 @@ void DtlsAssociation::startHandshake() if (!crypto.doHandshake(&socket)) emit errorMessage(tr("%1: failed to start a handshake - %2").arg(name, crypto.dtlsErrorString())); else{ - //socket.waitForBytesWritten(); while(true){ socket.waitForReadyRead(); if(crypto.isConnectionEncrypted()){ break; } } - //crypto.doHandshake(&socket); emit infoMessage(tr("%1: starting a handshake").arg(name)); } @@ -109,8 +87,7 @@ void DtlsAssociation::udpSocketConnected() void DtlsAssociation::readyRead() { - //QEventLoop loop; - //QThread::sleep(2); + if (socket.pendingDatagramSize() <= 0) { emit warningMessage(tr("%1: spurious read notification?").arg(name)); return; @@ -130,7 +107,6 @@ void DtlsAssociation::readyRead() if (crypto.isConnectionEncrypted()) { const QByteArray plainText = crypto.decryptDatagram(&socket, dgram); if (plainText.size()) { - //emit serverResponse(name, dgram, plainText, crypto.peerAddress(), crypto.peerPort(), socket.localPort()); emit receivedDatagram(plainText); return; } @@ -146,29 +122,23 @@ void DtlsAssociation::readyRead() } else { //! [7] //! [8] - if (!crypto.doHandshake(&socket, dgram)) { emit errorMessage(tr("%1: handshake error - %2").arg(name, crypto.dtlsErrorString())); return; } //! [8] - - //socket.waitForReadyRead(); crypto.doHandshake(&socket, dgram); //! [9] if (crypto.isConnectionEncrypted()) { emit infoMessage(tr("%1: encrypted connection established!").arg(name)); - //writeMassage(); emit handShakeComplited(); } else { //! [9] - //socket.waitForReadyRead(10000); emit infoMessage(tr("%1: continuing with handshake ...").arg(name)); } - //socket.waitForReadyRead(10000); } - //loop.exec(); + } @@ -191,6 +161,15 @@ void DtlsAssociation::pskRequired(QSslPreSharedKeyAuthenticator *auth) auth->setIdentity(name.toLatin1()); auth->setPreSharedKey(QByteArrayLiteral("\x1a\x2b\x3c\x4d\x5e\x6f")); } + + +void DtlsAssociation::setCipher(QString chosenCipher) { + configuration.setCiphers(chosenCipher); + //configuration.setProtocol(QSsl::DtlsV1_2); + crypto.setDtlsConfiguration(configuration); +} + +///////////////////////////////////////////////////backups////////////////////////////////// //! [14] //! [10] @@ -218,11 +197,6 @@ void DtlsAssociation::pskRequired(QSslPreSharedKeyAuthenticator *auth) -void DtlsAssociation::setCipher(QString chosenCipher) { - configuration.setCiphers(chosenCipher); - //configuration.setProtocol(QSsl::DtlsV1_2); - crypto.setDtlsConfiguration(configuration); -} //void DtlsAssociation::setKeyCertAndCaCert(QString keyPath, QString certPath, QString caPath) { // QFile keyFile(keyPath); diff --git a/src/dtlsthread.cpp b/src/dtlsthread.cpp index 96c5d24d..a31fd05f 100644 --- a/src/dtlsthread.cpp +++ b/src/dtlsthread.cpp @@ -18,37 +18,12 @@ Dtlsthread::~Dtlsthread() { void Dtlsthread::run() { closeRequest = false; - handShakeDone = false; - QSettings settings(SETTINGSFILE, QSettings::IniFormat); - if (settings.status() != QSettings::NoError) { - sendpacket.errorString ="Can't open settings file."; - } - - //the vector of cmdComponents contains: dataStr, toIp, toPort, sslPrivateKeyPath, sslLocalCertificatePath, sslCaFullPath, chosen cipher - std::vector cmdComponents = getCmdInput(sendpacket, settings); - //qdtls - - const QString ipAddress = cmdComponents[1]; - QHostAddress ipAddressHost; - ipAddressHost.setAddress(ipAddress); - quint16 port = cmdComponents[2].toUShort(); - DtlsAssociation *dtlsAssociationP = new DtlsAssociation(ipAddressHost, port, sendpacket.fromIP, cmdComponents); - dtlsAssociationP->socket; - sendpacket.fromPort = dtlsAssociationP->socket.localPort(); - connect(this, &Dtlsthread::serverResponse, this, &Dtlsthread::addServerResponse); - connect(dtlsAssociationP, &DtlsAssociation::receivedDatagram, this, &Dtlsthread::receivedDatagram); - PacketNetwork *parentNetwork = qobject_cast(parent()); - connect(this, SIGNAL(packetReceived(Packet)), parentNetwork, SLOT(toTrafficLog(Packet))); - dtlsAssociationP->setCipher(cmdComponents[6]); - dtlsAssociation = dtlsAssociationP; + dtlsAssociation = initDtlsAssociation(); connect(dtlsAssociation, &DtlsAssociation::handShakeComplited,this, &Dtlsthread::handShakeComplited); dtlsAssociation->startHandshake(); - writeMassage(sendpacket, dtlsAssociation); - persistentConnectionLoop(); - connectStatus("Connected"); } @@ -115,17 +90,12 @@ void Dtlsthread::handShakeComplited(){ } void Dtlsthread::writeMassage(Packet packetToSend, DtlsAssociation* dtlsAssociation){ - - //emit handShakeComplited(packetToSend, this); const qint64 written = dtlsAssociation->crypto.writeDatagramEncrypted(&(dtlsAssociation->socket), packetToSend.asciiString().toLatin1()); if (written <= 0) { //emit errorMessage(tr("%1: failed to send a ping - %2").arg(name, crypto.dtlsErrorString())); return; } emit packetSent(packetToSend); - //packetToSend.hexString = ""; - //dtlsAssociation->socket.waitForReadyRead(); - //addServerResponse() } @@ -351,9 +321,27 @@ void Dtlsthread::sendPersistant(Packet sendpacket) } void Dtlsthread::onTimeout(){ -// dtlsAssociation->socket.disconnectFromHost(); -// dtlsAssociation->socket.close(); -// //this->terminate(); closeRequest = true; timer->stop(); } + +DtlsAssociation* Dtlsthread::initDtlsAssociation(){ + QSettings settings(SETTINGSFILE, QSettings::IniFormat); + if (settings.status() != QSettings::NoError) { + sendpacket.errorString ="Can't open settings file."; + } + //the vector of cmdComponents contains: dataStr, toIp, toPort, sslPrivateKeyPath, sslLocalCertificatePath, sslCaFullPath, chosen cipher + std::vector cmdComponents = getCmdInput(sendpacket, settings); + const QString ipAddress = cmdComponents[1]; + QHostAddress ipAddressHost; + ipAddressHost.setAddress(ipAddress); + quint16 port = cmdComponents[2].toUShort(); + DtlsAssociation *dtlsAssociationP = new DtlsAssociation(ipAddressHost, port, sendpacket.fromIP, cmdComponents); + sendpacket.fromPort = dtlsAssociationP->socket.localPort(); + connect(this, &Dtlsthread::serverResponse, this, &Dtlsthread::addServerResponse); + connect(dtlsAssociationP, &DtlsAssociation::receivedDatagram, this, &Dtlsthread::receivedDatagram); + PacketNetwork *parentNetwork = qobject_cast(parent()); + connect(this, SIGNAL(packetReceived(Packet)), parentNetwork, SLOT(toTrafficLog(Packet))); + dtlsAssociationP->setCipher(cmdComponents[6]); + return dtlsAssociationP; +} diff --git a/src/dtlsthread.h b/src/dtlsthread.h index ccc1c5ad..d45b90da 100644 --- a/src/dtlsthread.h +++ b/src/dtlsthread.h @@ -16,6 +16,7 @@ class Dtlsthread : public QThread QTimer* timer; bool closeRequest; Dtlsthread(Packet sendPacket, QObject *parent); + DtlsAssociation* initDtlsAssociation(); virtual ~Dtlsthread(); std::vector dtlsAssociations; bool handShakeDone; From 4b838b12eae622d5ace29dee359a0be252894541 Mon Sep 17 00:00:00 2001 From: israel Date: Sun, 3 Dec 2023 19:49:42 +0200 Subject: [PATCH 44/79] orgenize the isDtls condition of PacketNetwork class --- src/packetnetwork.cpp | 57 ++++++------------------------------------- 1 file changed, 8 insertions(+), 49 deletions(-) diff --git a/src/packetnetwork.cpp b/src/packetnetwork.cpp index d40ee803..7bf46ddd 100755 --- a/src/packetnetwork.cpp +++ b/src/packetnetwork.cpp @@ -912,79 +912,38 @@ void PacketNetwork::packetToSend(Packet sendpacket) if(sendpacket.isDTLS()){ + Dtlsthread * thread = new Dtlsthread(sendpacket, this); QSettings settings(SETTINGSFILE, QSettings::IniFormat); + QDEBUG() << connect(thread, SIGNAL(packetReceived(Packet)), this, SLOT(packetReceivedECHO(Packet))) + << connect(thread, SIGNAL(toStatusBar(QString, int, bool)), this, SLOT(toStatusBarECHO(QString, int, bool))) + << connect(thread, SIGNAL(packetSent(Packet)), this, SLOT(packetSentECHO(Packet))) + << connect(thread, SIGNAL(destroyed()), this, SLOT(disconnected())); + if(settings.value("leaveSessionOpen").toString() == "true"){ PersistentConnection * pcWindow = new PersistentConnection(); - Dtlsthread * thread = new Dtlsthread(sendpacket, this); + pcWindow->sendPacket = sendpacket; pcWindow->init(); pcWindow->dthread = thread; - QDEBUG() << ": thread Connection attempt " << connect(pcWindow, SIGNAL(persistentPacketSend(Packet)), thread, SLOT(sendPersistant(Packet))) << connect(pcWindow, SIGNAL(closeConnection()), thread, SLOT(closeConnection())) << connect(thread, SIGNAL(connectStatus(QString)), pcWindow, SLOT(statusReceiver(QString))) << connect(thread, SIGNAL(packetSent(Packet)), pcWindow, SLOT(packetSentSlot(Packet))); - //connects from packetNetwork in isDtls condition - QDEBUG() << connect(thread, SIGNAL(packetReceived(Packet)), this, SLOT(packetReceivedECHO(Packet))) - << connect(thread, SIGNAL(toStatusBar(QString, int, bool)), this, SLOT(toStatusBarECHO(QString, int, bool))) - << connect(thread, SIGNAL(packetSent(Packet)), this, SLOT(packetSentECHO(Packet))); - QDEBUG() << connect(thread, SIGNAL(destroyed()), this, SLOT(disconnected())); - - - // QDEBUG() << connect(thread, SIGNAL(packetReceived(Packet)), this, SLOT(packetReceivedECHO(Packet))) - // << connect(thread, SIGNAL(toStatusBar(QString, int, bool)), this, SLOT(toStatusBarECHO(QString, int, bool))) - // << connect(thread, SIGNAL(packetSent(Packet)), this, SLOT(packetSentECHO(Packet))); - - - //connect(&packetNetwork, SIGNAL(packetSent(Packet)), - // this, SLOT(toTrafficLog(Packet))); pcWindow->show(); thread->start(); } else{ - // PersistentConnection * pcWindow = new PersistentConnection(); - Dtlsthread * thread = new Dtlsthread(sendpacket, this); - // pcWindow->sendPacket = sendpacket; - // pcWindow->init(); - // pcWindow->dthread = thread; - - - // QDEBUG() << ": thread Connection attempt " - // << connect(pcWindow, SIGNAL(persistentPacketSend(Packet)), thread, SLOT(sendPersistant(Packet))) - // << connect(pcWindow, SIGNAL(closeConnection()), thread, SLOT(closeConnection())) - // << connect(thread, SIGNAL(connectStatus(QString)), pcWindow, SLOT(statusReceiver(QString))) - // << connect(thread, SIGNAL(packetSent(Packet)), pcWindow, SLOT(packetSentSlot(Packet))); - //connects from packetNetwork in isDtls condition - QDEBUG() << connect(thread, SIGNAL(packetReceived(Packet)), this, SLOT(packetReceivedECHO(Packet))) - << connect(thread, SIGNAL(toStatusBar(QString, int, bool)), this, SLOT(toStatusBarECHO(QString, int, bool))) - << connect(thread, SIGNAL(packetSent(Packet)), this, SLOT(packetSentECHO(Packet))); - QDEBUG() << connect(thread, SIGNAL(destroyed()), this, SLOT(disconnected())); - - - // QDEBUG() << connect(thread, SIGNAL(packetReceived(Packet)), this, SLOT(packetReceivedECHO(Packet))) - // << connect(thread, SIGNAL(toStatusBar(QString, int, bool)), this, SLOT(toStatusBarECHO(QString, int, bool))) - // << connect(thread, SIGNAL(packetSent(Packet)), this, SLOT(packetSentECHO(Packet))); - - - //connect(&packetNetwork, SIGNAL(packetSent(Packet)), - // this, SLOT(toTrafficLog(Packet))); - - // pcWindow->show(); thread->start(); - //Network manager will manage this thread so the UI window doesn't need to. - dtlsthreadList.append(thread); - QTimer* timer = new QTimer(this); thread->timer = timer; connect(timer, SIGNAL(timeout()), thread, SLOT(onTimeout())); timer->start(1000); - } - + dtlsthreadList.append(thread); } From 3fd5f2f2db8c232766204eedca1202460df8c605 Mon Sep 17 00:00:00 2001 From: israel Date: Sun, 3 Dec 2023 20:15:49 +0200 Subject: [PATCH 45/79] more oranize in the header files --- src/association.cpp | 1 - src/association.h | 22 ++++----- src/dtlsthread.cpp | 40 ++++++++-------- src/dtlsthread.h | 25 +++++----- src/packetnetwork.cpp | 103 +++++++++++++++--------------------------- src/packetnetwork.h | 3 -- 6 files changed, 76 insertions(+), 118 deletions(-) diff --git a/src/association.cpp b/src/association.cpp index dd7359ae..40413631 100644 --- a/src/association.cpp +++ b/src/association.cpp @@ -165,7 +165,6 @@ void DtlsAssociation::pskRequired(QSslPreSharedKeyAuthenticator *auth) void DtlsAssociation::setCipher(QString chosenCipher) { configuration.setCiphers(chosenCipher); - //configuration.setProtocol(QSsl::DtlsV1_2); crypto.setDtlsConfiguration(configuration); } diff --git a/src/association.h b/src/association.h index ca86a950..f1521b53 100644 --- a/src/association.h +++ b/src/association.h @@ -13,36 +13,30 @@ class DtlsAssociation : public QObject Q_OBJECT public: - QDtls crypto; - QUdpSocket socket; - QString name; - Packet packetToSend; - DtlsAssociation(const QHostAddress &address, quint16 port, const QString &connectionName, std::vector cmdComponents); ~DtlsAssociation(); void startHandshake(); - //void setKeyCertAndCaCert(QString keyPath, QString certPath, QString caPath); void setCipher(QString chosenCipher); + QSslConfiguration configuration = QSslConfiguration::defaultDtlsConfiguration(); + QDtls crypto; + QUdpSocket socket; + QString name; + Packet packetToSend; signals: - void handShakeComplited(); void errorMessage(const QString &message); void warningMessage(const QString &message); void infoMessage(const QString &message); - void serverResponse(const QString &clientInfo, const QByteArray &datagraam, - const QByteArray &plainText, QHostAddress peerAddress, quint16 peerPort, quint16 clientPort); + void handShakeComplited(); void receivedDatagram(QByteArray plainText); private slots: void udpSocketConnected(); void readyRead(); - void handshakeTimeout(); void pskRequired(QSslPreSharedKeyAuthenticator *auth); - //void pingTimeout(); - //void writeMassage(); - + void handshakeTimeout(); private: @@ -52,6 +46,6 @@ private slots: Q_DISABLE_COPY(DtlsAssociation) }; -//! [0] + #endif // ASSOCIATION_H diff --git a/src/dtlsthread.cpp b/src/dtlsthread.cpp index a31fd05f..c0d3648a 100644 --- a/src/dtlsthread.cpp +++ b/src/dtlsthread.cpp @@ -64,25 +64,7 @@ std::vector Dtlsthread::getCmdInput(Packet sendpacket, QSettings& setti return cmdComponents; } -void Dtlsthread::addServerResponse(const QString &clientAddress, const QByteArray &datagram, const QByteArray &plainText, QHostAddress serverAddress, quint16 serverPort, quint16 userPort) -{ - //ned to fix the "to port" field - //find a way do reach the client data (maybe use the clientInfo) inorder to present the "ToAddress" and "To Port" fields in the traffic log area - //QStringList clientIpAndPort = clientInfo.split(':', Qt::KeepEmptyParts); - - Packet recPacket; - recPacket.init(); - recPacket.fromIP = serverAddress.toString(); - recPacket.fromPort = serverPort; - QString massageFromTheOtherPeer = QString::fromUtf8(plainText); - recPacket.hexString = massageFromTheOtherPeer; - recPacket.toIP = clientAddress; - recPacket.port = userPort; - recPacket.errorString = "none"; - recPacket.tcpOrUdp = "DTLS"; - //emit packetReceived(recPacket); -} void Dtlsthread::handShakeComplited(){ @@ -338,10 +320,30 @@ DtlsAssociation* Dtlsthread::initDtlsAssociation(){ quint16 port = cmdComponents[2].toUShort(); DtlsAssociation *dtlsAssociationP = new DtlsAssociation(ipAddressHost, port, sendpacket.fromIP, cmdComponents); sendpacket.fromPort = dtlsAssociationP->socket.localPort(); - connect(this, &Dtlsthread::serverResponse, this, &Dtlsthread::addServerResponse); connect(dtlsAssociationP, &DtlsAssociation::receivedDatagram, this, &Dtlsthread::receivedDatagram); PacketNetwork *parentNetwork = qobject_cast(parent()); connect(this, SIGNAL(packetReceived(Packet)), parentNetwork, SLOT(toTrafficLog(Packet))); dtlsAssociationP->setCipher(cmdComponents[6]); return dtlsAssociationP; } + +/////////////////////////backups/////////////////// +//void Dtlsthread::addServerResponse(const QString &clientAddress, const QByteArray &datagram, const QByteArray &plainText, QHostAddress serverAddress, quint16 serverPort, quint16 userPort) +//{ +// //ned to fix the "to port" field +// //find a way do reach the client data (maybe use the clientInfo) inorder to present the "ToAddress" and "To Port" fields in the traffic log area +// //QStringList clientIpAndPort = clientInfo.split(':', Qt::KeepEmptyParts); + +// Packet recPacket; +// recPacket.init(); +// recPacket.fromIP = serverAddress.toString(); +// recPacket.fromPort = serverPort; +// QString massageFromTheOtherPeer = QString::fromUtf8(plainText); +// recPacket.hexString = massageFromTheOtherPeer; +// recPacket.toIP = clientAddress; +// recPacket.port = userPort; +// recPacket.errorString = "none"; +// recPacket.tcpOrUdp = "DTLS"; + +// //emit packetReceived(recPacket); +//} diff --git a/src/dtlsthread.h b/src/dtlsthread.h index d45b90da..a4a9bed1 100644 --- a/src/dtlsthread.h +++ b/src/dtlsthread.h @@ -11,37 +11,34 @@ class Dtlsthread : public QThread { Q_OBJECT - public: - QTimer* timer; - bool closeRequest; Dtlsthread(Packet sendPacket, QObject *parent); DtlsAssociation* initDtlsAssociation(); virtual ~Dtlsthread(); - std::vector dtlsAssociations; - bool handShakeDone; void persistentConnectionLoop(); + void run() override; // Pure virtual function making this class abstract + std::vector getCmdInput(Packet sendpacket, QSettings& settings); + void writeMassage(Packet packetToSend, DtlsAssociation* dtlsAssociation); + + QTimer* timer; + std::vector dtlsAssociations; DtlsAssociation* dtlsAssociation; - bool insidePersistent; QString recievedMassage; + Packet sendpacket; + bool closeRequest; + bool handShakeDone; + bool insidePersistent; - - Packet sendpacket; - void run() override; // Pure virtual function making this class abstract - //void sendPersistant(); - std::vector getCmdInput(Packet sendpacket, QSettings& settings); - void writeMassage(Packet packetToSend, DtlsAssociation* dtlsAssociation); public slots: void onTimeout(); void sendPersistant(Packet sendpacket); void handShakeComplited(); void receivedDatagram(QByteArray plainText); - void addServerResponse(const QString &clientInfo, const QByteArray &datagram, const QByteArray &plainText, QHostAddress serverAddress, quint16 serverPort, quint16 userPort); + signals: void connectStatus(QString); void packetSent(Packet); - void serverResponse(const QString &clientInfo, const QByteArray &datagraam, const QByteArray &plainText, QHostAddress peerAddress, quint16 peerPort, quint16 clientPort); void packetReceived(Packet); diff --git a/src/packetnetwork.cpp b/src/packetnetwork.cpp index 7bf46ddd..2d3a7542 100755 --- a/src/packetnetwork.cpp +++ b/src/packetnetwork.cpp @@ -1068,73 +1068,6 @@ void PacketNetwork::packetToSend(Packet sendpacket) } -//isDTLS function -void PacketNetwork::execCmd(QString opensslPath, DWORD& statusRef, Packet& sendpacket){ - //adjust the opensslPath to be the input for CreateProcess function - std::wstring wstr = opensslPath.toStdWString(); - //initiate the proccess's parameters - LPWSTR lpwstr = &wstr[0]; - STARTUPINFO si; - si.lpTitle = NULL; - PROCESS_INFORMATION pi; - ZeroMemory(&si, sizeof(si)); - si.cb = sizeof(si); - ZeroMemory(&pi, sizeof(pi)); - // Create the process in hidden mode - if (CreateProcess(NULL, lpwstr, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) { - WaitForSingleObject(pi.hProcess, 10000); - //if the connection doesn't established change modify the session to close session - GetExitCodeProcess(pi.hProcess, &statusRef); - //connection error - if (statusRef!=0){ - MainWindow::isSessionOpen = false; - sendpacket.errorString = "Connection error, openssl error code is: " + QString::number(static_cast(statusRef)); - } - CloseHandle(pi.hProcess); - CloseHandle(pi.hThread); - } else { - // error with the process creation - sendpacket.errorString = "process creation was faild"; - } -} - -std::vector PacketNetwork::getCmdInput(Packet sendpacket, QSettings& settings){ - //the array of cmdComponents: dataStr, toIp, toPort, sslPrivateKeyPath, sslLocalCertificatePath, sslCaFullPath - std::vector cmdComponents; - - //get the data of the packet - cmdComponents.push_back(QString::fromUtf8(sendpacket.getByteArray())); - cmdComponents.push_back(sendpacket.toIP); - cmdComponents.push_back(QString::number(sendpacket.port)); - - //get the pathes for verification from the settings - cmdComponents.push_back(settings.value("sslPrivateKeyPath", "default").toString()); - cmdComponents.push_back(settings.value("sslLocalCertificatePath", "default").toString()); - QString sslCaPath = settings.value("sslCaPath", "default").toString(); - - //get the full path to to ca-signed-cert.pem file - QDir dir(sslCaPath); - if (dir.exists()) { - QStringList nameFilters; - nameFilters << "*.pem"; // Filter for .txt files - - dir.setNameFilters(nameFilters); - QStringList fileList = dir.entryList(); - - if (!fileList.isEmpty()) { - // Select the first file that matches the filter - cmdComponents.push_back(dir.filePath(fileList.first())); - } else { - qDebug() << "No matching files found."; - } - } else { - qDebug() << "Directory does not exist."; - } - cmdComponents.push_back(settings.value("cipher", "AES256-GCM-SHA384").toString()); - return cmdComponents; -} - - void PacketNetwork::httpError(QNetworkRequest* pReply) { QDEBUGVAR(pReply); @@ -1253,6 +1186,42 @@ QList PacketNetwork::allTCPServers() return theServers; } +std::vector PacketNetwork::getCmdInput(Packet sendpacket, QSettings& settings){ + //the array of cmdComponents: dataStr, toIp, toPort, sslPrivateKeyPath, sslLocalCertificatePath, sslCaFullPath + std::vector cmdComponents; + + //get the data of the packet + cmdComponents.push_back(QString::fromUtf8(sendpacket.getByteArray())); + cmdComponents.push_back(sendpacket.toIP); + cmdComponents.push_back(QString::number(sendpacket.port)); + + //get the pathes for verification from the settings + cmdComponents.push_back(settings.value("sslPrivateKeyPath", "default").toString()); + cmdComponents.push_back(settings.value("sslLocalCertificatePath", "default").toString()); + QString sslCaPath = settings.value("sslCaPath", "default").toString(); + + //get the full path to to ca-signed-cert.pem file + QDir dir(sslCaPath); + if (dir.exists()) { + QStringList nameFilters; + nameFilters << "*.pem"; // Filter for .txt files + + dir.setNameFilters(nameFilters); + QStringList fileList = dir.entryList(); + + if (!fileList.isEmpty()) { + // Select the first file that matches the filter + cmdComponents.push_back(dir.filePath(fileList.first())); + } else { + qDebug() << "No matching files found."; + } + } else { + qDebug() << "Directory does not exist."; + } + cmdComponents.push_back(settings.value("cipher", "AES256-GCM-SHA384").toString()); + return cmdComponents; +} + void PacketNetwork::addServerResponse(const QString &clientAddress, const QByteArray &datagram, const QByteArray &plainText, QHostAddress serverAddress, quint16 serverPort, quint16 userPort) { //ned to fix the "to port" field diff --git a/src/packetnetwork.h b/src/packetnetwork.h index faf27fa8..34fd1992 100755 --- a/src/packetnetwork.h +++ b/src/packetnetwork.h @@ -45,8 +45,6 @@ class PacketNetwork : public QObject explicit PacketNetwork(QObject *parent = nullptr); void init(); - //isDTLS function - void execCmd(QString opensslPath, DWORD& statusRef, Packet& sendpacket); std::vector getCmdInput(Packet sendpacket, QSettings &settings); QString debugQByteArray(QByteArray debugArray); @@ -115,7 +113,6 @@ public slots: void readPendingDatagrams(); void disconnected(); void packetToSend(Packet sendpacket); - //void addServerResponse(const QString &clientInfo, const QByteArray &datagram, const QByteArray &plainText, QHostAddress peerAddress, quint16 peerPort); void addServerResponse(const QString &clientInfo, const QByteArray &datagram, const QByteArray &plainText, QHostAddress serverAddress, quint16 serverPort, quint16 userPort); From 8293bd01015a6d239f6608186d90c667f4ec6390 Mon Sep 17 00:00:00 2001 From: israel Date: Sun, 3 Dec 2023 20:42:42 +0200 Subject: [PATCH 46/79] persistent DTLS checkBox moved to the mainWindow.ui --- src/mainwindow.cpp | 92 +++++++------------------------------------ src/mainwindow.h | 17 +++----- src/mainwindow.ui | 61 +++++++--------------------- src/packetnetwork.cpp | 38 +++++++++--------- src/packetnetwork.h | 1 - src/settings.cpp | 12 ++---- src/settings.h | 1 - src/settings.ui | 7 ---- 8 files changed, 59 insertions(+), 170 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index c0e9a569..ba854723 100755 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -73,6 +73,18 @@ MainWindow::MainWindow(QWidget *parent) : { ui->setupUi(this); + QCheckBox* leaveSessionOpen; + + QSettings settings(SETTINGSFILE, QSettings::IniFormat); + + if(settings.value("leaveSessionOpen").toString() == "false"){ + ui->leaveSessionOpen->setChecked(false); + } else { + ui->leaveSessionOpen->setChecked(true); + } + + leaveSessionOpen = ui->leaveSessionOpen; + connect(leaveSessionOpen, &QCheckBox::toggled, this, &MainWindow::on_leaveSessionOpen_StateChanged); cipherCb = ui->cipherCb; //add the combobox the correct cipher suites @@ -82,20 +94,13 @@ MainWindow::MainWindow(QWidget *parent) : } if ( ui->udptcpComboBox->currentText().toLower() != "dtls"){ - ui->loadKeyButton->hide(); - ui->loadCertButton->hide(); - ui->noteServer->hide(); + ui->leaveSessionOpen->hide(); cipherCb->hide(); ui->CipherLable->hide(); } connect(cipherCb, &QComboBox::editTextChanged, this, &MainWindow::on_cipherCb_currentIndexChanged); - connect(loadKeyButton, &QPushButton::clicked, this, &MainWindow::on_loadKeyButton_clicked); - connect(loadCertButton, &QPushButton::clicked, this, &MainWindow::on_loadCertButton_clicked); - - QSettings settings(SETTINGSFILE, QSettings::IniFormat); - //settings.setValue("leaveSessionOpen", "false"); QIcon mIcon(":pslogo.png"); @@ -2656,69 +2661,6 @@ void MainWindow::on_loadFileButton_clicked() } -void MainWindow::on_loadKeyButton_clicked() -{ - static QString fileName; - static bool showWarning = true; - - if (fileName.isEmpty()) { - fileName = QDir::homePath(); - } - - fileName = QFileDialog::getOpenFileName(this, tr("Import File"), - fileName, - tr("*.*")); - - QDEBUGVAR(fileName); - - if (fileName.isEmpty()) { - QMessageBox::critical(this, "Error", "The uploaded file is empty."); - return; - // - } - - packetNetwork.keyPath = fileName; - - // Extract the file name from the full path - QFileInfo fileInfo(fileName); - QString fileNameOnly = fileInfo.fileName(); - - // Set the extracted file name as the text on the button - ui->loadKeyButton->setText(fileNameOnly); - -} - -void MainWindow::on_loadCertButton_clicked() -{ - static QString fileName; - static bool showWarning = true; - - if (fileName.isEmpty()) { - fileName = QDir::homePath(); - } - - fileName = QFileDialog::getOpenFileName(this, tr("Import File"), - fileName, - tr("*.*")); - - QDEBUGVAR(fileName); - - if (fileName.isEmpty()) { - QMessageBox::critical(this, "Error", "The uploaded file is empty."); - return; - // - } - - packetNetwork.certPath = fileName; - - // Extract the file name from the full path - QFileInfo fileInfo(fileName); - QString fileNameOnly = fileInfo.fileName(); - - // Set the extracted file name as the text on the button - ui->loadCertButton->setText(fileNameOnly); - -} void MainWindow::on_actionDonate_Thank_You_triggered() { @@ -2737,15 +2679,11 @@ void MainWindow::on_udptcpComboBox_currentIndexChanged(const QString &arg1) /////////////////////////////////dtls add line edit for adding path for cert if ( ui->udptcpComboBox->currentText().toLower() == "dtls") { - ui->loadKeyButton->show(); // Enable when "dtls" is selected - ui->loadCertButton->show(); - ui->noteServer->show(); + ui->leaveSessionOpen->show(); cipherCb->show(); ui->CipherLable->show(); } else { - ui->loadKeyButton->hide(); // Disable for other options - ui->loadCertButton->hide(); // Disable for other options - ui->noteServer->hide(); + ui->leaveSessionOpen->hide(); cipherCb->hide(); ui->CipherLable->hide(); diff --git a/src/mainwindow.h b/src/mainwindow.h index 03584572..13824c38 100755 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -41,6 +41,8 @@ class PreviewFilter : public QObject Q_OBJECT public: + QCheckBox* leaveSessionOpen; + explicit PreviewFilter(QObject * parent, QLineEdit * asciiEdit, QLineEdit * hexEdit) : QObject{parent}, asciiEdit{asciiEdit}, hexEdit{hexEdit} { @@ -70,18 +72,12 @@ class MainWindow : public QMainWindow Q_OBJECT public: - static int isSessionOpen; explicit MainWindow(QWidget *parent = nullptr); ~MainWindow(); QString ASCIITohex(QString &ascii); QString hexToASCII(QString &hex); - - void loadPacketsTable(); - - QComboBox* cipherCb; - QPushButton *loadKeyButton; - QPushButton *loadCertButton; + void loadPacketsTable(); QPushButton *generatePSLink(); QPushButton *generateDNLink(); void populateTableRow(int rowCounter, Packet tempPacket); @@ -92,9 +88,11 @@ class MainWindow : public QMainWindow int findColumnIndex(QListWidget *lw, QString search); void packetTable_checkMultiSelected(); void generateConnectionMenu(); - void updateManager(QByteArray response); + QComboBox* cipherCb; + static int isSessionOpen; + signals: void sendPacket(Packet sendpacket); @@ -215,9 +213,6 @@ class MainWindow : public QMainWindow void on_loadFileButton_clicked(); - void on_loadKeyButton_clicked(); - - void on_loadCertButton_clicked(); void on_actionDonate_Thank_You_triggered(); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index a6c60260..684da032 100755 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -334,49 +334,10 @@ 16777215 - - - - 90 - 0 - 111 - 21 - - - - Load Certificate File - - - - - - 0 - 0 - 80 - 21 - - - - Load Key File - - - - - - 210 - 0 - 161 - 21 - - - - *Only if needed by the server - - - 650 + 670 0 191 24 @@ -481,7 +442,7 @@ - 560 + 590 0 81 20 @@ -491,11 +452,19 @@ Cipher Suites: - loadKeyButton - loadCertButton - noteServer - cipherCb - CipherLable + + + + 430 + 0 + 111 + 22 + + + + Persistent DTLS + + diff --git a/src/packetnetwork.cpp b/src/packetnetwork.cpp index 2d3a7542..87523cbb 100755 --- a/src/packetnetwork.cpp +++ b/src/packetnetwork.cpp @@ -1222,23 +1222,23 @@ std::vector PacketNetwork::getCmdInput(Packet sendpacket, QSettings& se return cmdComponents; } -void PacketNetwork::addServerResponse(const QString &clientAddress, const QByteArray &datagram, const QByteArray &plainText, QHostAddress serverAddress, quint16 serverPort, quint16 userPort) -{ - //ned to fix the "to port" field - //find a way do reach the client data (maybe use the clientInfo) inorder to present the "ToAddress" and "To Port" fields in the traffic log area - //QStringList clientIpAndPort = clientInfo.split(':', Qt::KeepEmptyParts); - - Packet recPacket; - recPacket.init(); - recPacket.fromIP = serverAddress.toString(); - recPacket.fromPort = serverPort; - QString massageFromTheOtherPeer = QString::fromUtf8(plainText); - recPacket.hexString = massageFromTheOtherPeer; - recPacket.toIP = clientAddress; - recPacket.port = userPort; - recPacket.errorString = "none"; - recPacket.tcpOrUdp = "DTLS"; - - emit packetReceived(recPacket); -} +//void PacketNetwork::addServerResponse(const QString &clientAddress, const QByteArray &datagram, const QByteArray &plainText, QHostAddress serverAddress, quint16 serverPort, quint16 userPort) +//{ +// //ned to fix the "to port" field +// //find a way do reach the client data (maybe use the clientInfo) inorder to present the "ToAddress" and "To Port" fields in the traffic log area +// //QStringList clientIpAndPort = clientInfo.split(':', Qt::KeepEmptyParts); + +// Packet recPacket; +// recPacket.init(); +// recPacket.fromIP = serverAddress.toString(); +// recPacket.fromPort = serverPort; +// QString massageFromTheOtherPeer = QString::fromUtf8(plainText); +// recPacket.hexString = massageFromTheOtherPeer; +// recPacket.toIP = clientAddress; +// recPacket.port = userPort; +// recPacket.errorString = "none"; +// recPacket.tcpOrUdp = "DTLS"; + +// emit packetReceived(recPacket); +//} diff --git a/src/packetnetwork.h b/src/packetnetwork.h index 34fd1992..93144013 100755 --- a/src/packetnetwork.h +++ b/src/packetnetwork.h @@ -113,7 +113,6 @@ public slots: void readPendingDatagrams(); void disconnected(); void packetToSend(Packet sendpacket); - void addServerResponse(const QString &clientInfo, const QByteArray &datagram, const QByteArray &plainText, QHostAddress serverAddress, quint16 serverPort, quint16 userPort); private slots: diff --git a/src/settings.cpp b/src/settings.cpp index 056957a8..76be5ff8 100755 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -93,18 +93,15 @@ Settings::Settings(QWidget *parent, MainWindow* mw) : ui->setupUi(this); setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint); + QSettings settings(SETTINGSFILE, QSettings::IniFormat); + //not working yet... ui->multiSendDelayLabel->hide(); ui->multiSendDelayEdit->hide(); //connect(loadKeyButton, &QPushButton::clicked, this, &MainWindow::on_loadKeyButton_clicked); - QSettings settings(SETTINGSFILE, QSettings::IniFormat); - if(settings.value("leaveSessionOpen").toString() == "false"){ - ui->leaveSessionOpen->setChecked(false); - } else { - ui->leaveSessionOpen->setChecked(true); - } + QIcon mIcon(":pslogo.png"); setWindowTitle("Packet Sender "+tr("Settings")); @@ -180,8 +177,7 @@ Settings::Settings(QWidget *parent, MainWindow* mw) : ui->dateFormatExample->setText(now.toString(dateFormat)); ui->timeFormatExample->setText(now.toString(timeFormat)); - leaveSessionOpen = ui->leaveSessionOpen; - connect(leaveSessionOpen, &QCheckBox::toggled, dynamic_cast(parent), &MainWindow::on_leaveSessionOpen_StateChanged); + connect(ui->dateFormat, &QLineEdit::textChanged, this, [=](QString val) { // use action as you wish diff --git a/src/settings.h b/src/settings.h index 0728637c..37cfd63b 100644 --- a/src/settings.h +++ b/src/settings.h @@ -71,7 +71,6 @@ class Settings : public QDialog explicit Settings(QWidget *parent = nullptr, MainWindow* mw = nullptr); ~Settings(); - QCheckBox* leaveSessionOpen; static QStringList defaultPacketTableHeader(); static QStringList defaultTrafficTableHeader(); diff --git a/src/settings.ui b/src/settings.ui index 54fe7a8e..6c66a351 100755 --- a/src/settings.ui +++ b/src/settings.ui @@ -329,13 +329,6 @@ - - - - Leave the session open - - - From 7366b0aa9bb7d12f7073e0d089ade549f3ce23c0 Mon Sep 17 00:00:00 2001 From: israel Date: Sun, 3 Dec 2023 20:50:45 +0200 Subject: [PATCH 47/79] two non-usable buttons removed from the mainWindow.ui --- src/mainwindow.ui | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 684da032..d9de537f 100755 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -337,7 +337,7 @@ - 670 + 660 0 191 24 @@ -442,7 +442,7 @@ - 590 + 570 0 81 20 From 5b5d4c042928b37d9cf1f0bc5cac712db44c4d32 Mon Sep 17 00:00:00 2001 From: israel Date: Mon, 4 Dec 2023 08:32:17 +0200 Subject: [PATCH 48/79] some basic ui changes in the Network tab --- src/settings.ui | 47 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/src/settings.ui b/src/settings.ui index 6c66a351..9f247fcc 100755 --- a/src/settings.ui +++ b/src/settings.ui @@ -105,6 +105,40 @@ + + + + Enable DTLS Servers + + + + + + + + + + 0 + 0 + + + + DTLS Server Port (comma-separated, 0 for random) + + + + + + + Qt::LeftToRight + + + 0 + + + + + @@ -175,19 +209,6 @@ - - - - Qt::Vertical - - - - 20 - 40 - - - - From bdb4fc3e320bcaff057b65b94dde28b56bfba190 Mon Sep 17 00:00:00 2001 From: israel Date: Mon, 4 Dec 2023 10:51:30 +0200 Subject: [PATCH 49/79] update the DTLSServerStatus on the mainWindow.ui --- src/mainwindow.cpp | 4 ++-- src/packetnetwork.cpp | 33 +++++++++++++++++++++++++++++++-- src/settings.cpp | 11 ++++++++++- src/settings.ui | 4 ++-- 4 files changed, 45 insertions(+), 7 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ba854723..29bee876 100755 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -802,8 +802,8 @@ void MainWindow::DTLSServerStatus() { if (packetNetwork.DTLSListening()) { - QString ports = packetNetwork.getUDPPortString(); - int portcount = packetNetwork.getUDPPortsBound().size(); + QString ports = packetNetwork.getDTLSPortString(); + int portcount = packetNetwork.getDTLSPortsBound().size(); dtlsServerStatus->setToolTip(ports); if(portcount > 3) { dtlsServerStatus->setText("DTLS: " + QString::number(portcount) + tr(" Ports")); diff --git a/src/packetnetwork.cpp b/src/packetnetwork.cpp index 87523cbb..e535bbb9 100755 --- a/src/packetnetwork.cpp +++ b/src/packetnetwork.cpp @@ -260,7 +260,7 @@ void PacketNetwork::init() int tcpPort = 0; int sslPort = 0; - settings.setValue("dtlsPort", "12346"); + //settings.setValue("dtlsPort", "12346"); dtlsPortList = Settings::portsToIntList(settings.value("dtlsPort", "0").toString()); udpPortList = Settings::portsToIntList(settings.value("udpPort", "0").toString()); tcpPortList = Settings::portsToIntList(settings.value("tcpPort", "0").toString()); @@ -285,7 +285,9 @@ void PacketNetwork::init() QUdpSocket *udpSocket, *dtlsSocket; ThreadedTCPServer *ssl, *tcp; foreach (dtlsPort, dtlsPortList) { - + /////////////////////////////////after adding the DtlsServer class////////////////// + //DtlsServer dtlsServer; + //bool bindResult = dtlsServer.serverSocket.listen(IPV4_OR_IPV6, dtlsPort); dtlsSocket = new QUdpSocket(this); @@ -318,6 +320,9 @@ void PacketNetwork::init() if(bindResult) { dtlsServers.append(dtlsSocket); +////////////////////////////////////////after adding the DtlsServer class////////////////// + + //dtlsServers.append(dtlsServer.serverSocket); } } @@ -439,6 +444,30 @@ void PacketNetwork::init() delayAfterConnect = 500; } + if (activateDTLS) { + foreach (dtlsSocket, dtlsServers) { +/////////////////////////////////after adding the DtlsServer class////////////////// +// +// connect(&server, &DtlsServer::errorMessage, this, &MainWindow::addErrorMessage); +// connect(&server, &DtlsServer::warningMessage, this, &MainWindow::addWarningMessage); +// connect(&server, &DtlsServer::infoMessage, this, &MainWindow::addInfoMessage); +// connect(&server, &DtlsServer::datagramReceived, this, &MainWindow::addClientMessage); +// + QDEBUG() << "signal/slot datagram connect: " << connect(dtlsSocket, SIGNAL(readyRead()), + this, SLOT(readPendingDatagrams())); + } + + } else { + QDEBUG() << "udp server disable"; + foreach (udpSocket, udpServers) { + udpSocket->close(); + } + udpServers.clear(); + + } + + + if (activateUDP) { foreach (udpSocket, udpServers) { QDEBUG() << "signal/slot datagram connect: " << connect(udpSocket, SIGNAL(readyRead()), diff --git a/src/settings.cpp b/src/settings.cpp index 76be5ff8..a5ce8590 100755 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -201,6 +201,7 @@ Settings::Settings(QWidget *parent, MainWindow* mw) : ui->resolveDNSOnInputCheck->setChecked(settings.value("resolveDNSOnInputCheck", false).toBool()); + QList dtlsList = portsToIntList(settings.value("dtlsPort", "0").toString()); QList udpList = portsToIntList(settings.value("udpPort", "0").toString()); QList tcpList = portsToIntList(settings.value("tcpPort", "0").toString()); QList sslList = portsToIntList(settings.value("sslPort", "0").toString()); @@ -208,11 +209,15 @@ Settings::Settings(QWidget *parent, MainWindow* mw) : ui->udpServerPortEdit->setText(intListToPorts(udpList)); ui->tcpServerPortEdit->setText(intListToPorts(tcpList)); ui->sslServerPortEdit->setText(intListToPorts(sslList)); + ui->dtlsServerPortEdit->setText(intListToPorts(dtlsList)); + ui->udpServerEnableCheck->setChecked(settings.value("udpServerEnable", true).toBool()); ui->tcpServerEnableCheck->setChecked(settings.value("tcpServerEnable", true).toBool()); ui->sslServerEnableCheck->setChecked(settings.value("sslServerEnable", true).toBool()); + ui->dtlsServerEnableCheck->setChecked(settings.value("sslServerEnable", true).toBool()); + ui->serverSnakeOilCheck->setChecked(settings.value("serverSnakeOilCheck", true).toBool()); @@ -352,6 +357,8 @@ void Settings::on_buttonBox_accepted() QList udpList = Settings::portsToIntList(ui->udpServerPortEdit->text()); QList tcpList = Settings::portsToIntList(ui->tcpServerPortEdit->text()); QList sslList = Settings::portsToIntList(ui->sslServerPortEdit->text()); + QList dtlsList = Settings::portsToIntList(ui->dtlsServerPortEdit->text()); + if(ui->ipSpecificRadio->isChecked()) { QHostAddress address(ui->bindIPAddress->text()); @@ -394,7 +401,7 @@ void Settings::on_buttonBox_accepted() } - //settings.setValue("dtlsPort", intListToPorts(dtlsList)); + settings.setValue("dtlsPort", intListToPorts(dtlsList)); settings.setValue("udpPort", intListToPorts(udpList)); settings.setValue("tcpPort", intListToPorts(tcpList)); settings.setValue("sslPort", intListToPorts(sslList)); @@ -407,6 +414,8 @@ void Settings::on_buttonBox_accepted() settings.setValue("udpServerEnable", ui->udpServerEnableCheck->isChecked()); settings.setValue("tcpServerEnable", ui->tcpServerEnableCheck->isChecked()); settings.setValue("sslServerEnable", ui->sslServerEnableCheck->isChecked()); + settings.setValue("dtlsServerEnable", ui->dtlsServerEnableCheck->isChecked()); + settings.setValue("serverSnakeOilCheck", ui->serverSnakeOilCheck->isChecked()); diff --git a/src/settings.ui b/src/settings.ui index 9f247fcc..fd4354e1 100755 --- a/src/settings.ui +++ b/src/settings.ui @@ -106,7 +106,7 @@ - + Enable DTLS Servers @@ -128,7 +128,7 @@ - + Qt::LeftToRight From 95d915d32dd52bc2d872846c78468dd4f39aa636 Mon Sep 17 00:00:00 2001 From: israel Date: Mon, 4 Dec 2023 21:20:43 +0200 Subject: [PATCH 50/79] the client and server are working via persistent and non-persistent, need only to connect the correct events to the trafficLog --- src/PacketSender.pro | 2 + src/dtlsserver.cpp | 245 ++++++++++++++++++++++++++++++++++++++++++ src/dtlsserver.h | 60 +++++++++++ src/mainwindow.cpp | 2 +- src/packetnetwork.cpp | 55 ++++++---- src/packetnetwork.h | 6 +- 6 files changed, 345 insertions(+), 25 deletions(-) create mode 100644 src/dtlsserver.cpp create mode 100644 src/dtlsserver.h diff --git a/src/PacketSender.pro b/src/PacketSender.pro index 6f4a565b..fb4cadc4 100755 --- a/src/PacketSender.pro +++ b/src/PacketSender.pro @@ -19,6 +19,7 @@ TRANSLATIONS += languages/packetsender_en.ts \ SOURCES += mainwindow.cpp \ association.cpp \ + dtlsserver.cpp \ dtlsthread.cpp \ languagechooser.cpp \ panel.cpp \ @@ -38,6 +39,7 @@ SOURCES += mainwindow.cpp \ HEADERS += mainwindow.h \ association.h \ + dtlsserver.h \ dtlsthread.h \ languagechooser.h \ panel.h \ diff --git a/src/dtlsserver.cpp b/src/dtlsserver.cpp new file mode 100644 index 00000000..7043427d --- /dev/null +++ b/src/dtlsserver.cpp @@ -0,0 +1,245 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "dtlsserver.h" + +#include + +namespace { + +QString peer_info(const QHostAddress &address, quint16 port) +{ + const static QString info = QStringLiteral("(%1:%2)"); + return info.arg(address.toString()).arg(port); +} + +QString connection_info(QDtls *connection) +{ + QString info(DtlsServer::tr("Session cipher: ")); + info += connection->sessionCipher().name(); + + info += DtlsServer::tr("; session protocol: "); + switch (connection->sessionProtocol()) { + case QSsl::DtlsV1_2: + info += DtlsServer::tr("DTLS 1.2."); + break; + default: + info += DtlsServer::tr("Unknown protocol."); + } + + return info; +} + +} // unnamed namespace + +//! [1] +DtlsServer::DtlsServer() +{ + connect(&serverSocket, &QAbstractSocket::readyRead, this, &DtlsServer::readyRead); + ///////////////////////////////////////////////////// + QFile certFile("C:/Users/israe/OneDrive - ort braude college of engineering/rsa_encryption/server-signed-cert.pem"); + if(!certFile.open(QIODevice::ReadOnly)){ + return; + } + QSslCertificate certificate(&certFile, QSsl::Pem); + + QFile keyFile("C:/Users/israe/OneDrive - ort braude college of engineering/rsa_encryption/server-key.pem"); + if(!keyFile.open(QIODevice::ReadOnly)){ + return; + } + QSslKey privateKey(&keyFile, QSsl::Rsa); // Or QSsl::Ec if your key is ECDSA + + QFile caCertFile("C:/Users/israe/OneDrive - ort braude college of engineering/rsa_encryption/ca-signed-cert/signed-cert.pem"); + if(!caCertFile.open(QIODevice::ReadOnly)){ + return; + } + QSslCertificate caCertificate(&caCertFile, QSsl::Pem); + + serverConfiguration = QSslConfiguration::defaultDtlsConfiguration(); + serverConfiguration.setLocalCertificate(certificate); + serverConfiguration.setPrivateKey(privateKey); + serverConfiguration.setCaCertificates(QList() << caCertificate); + serverConfiguration.setPeerVerifyMode(QSslSocket::VerifyPeer); + /////////////////////////////////////////////////////////////////// + + //serverConfiguration = QSslConfiguration::defaultDtlsConfiguration(); + //serverConfiguration.setPreSharedKeyIdentityHint("Qt DTLS example server"); + //serverConfiguration.setPeerVerifyMode(QSslSocket::VerifyPeer); + //cookieSender.setDtlsConfiguration(dtlsConfiguration); +} +//! [1] + +DtlsServer::~DtlsServer() +{ + shutdown(); +} + +//! [2] +bool DtlsServer::listen(const QHostAddress &address, quint16 port) +{ + if (address != serverSocket.localAddress() || port != serverSocket.localPort()) { + shutdown(); + listening = serverSocket.bind(address, port); + if (!listening) + emit errorMessage(serverSocket.errorString()); + } else { + listening = true; + } + + return listening; +} +//! [2] + +bool DtlsServer::isListening() const +{ + return listening; +} + +void DtlsServer::close() +{ + listening = false; +} + +void DtlsServer::readyRead() +{ + //! [3] + const qint64 bytesToRead = serverSocket.pendingDatagramSize(); + if (bytesToRead <= 0) { + emit warningMessage(tr("A spurious read notification")); + return; + } + + QByteArray dgram(bytesToRead, Qt::Uninitialized); + QHostAddress peerAddress; + quint16 peerPort = 0; + const qint64 bytesRead = serverSocket.readDatagram(dgram.data(), dgram.size(), + &peerAddress, &peerPort); + if (bytesRead <= 0) { + emit warningMessage(tr("Failed to read a datagram: ") + serverSocket.errorString()); + return; + } + + dgram.resize(bytesRead); + //! [3] + //! [4] + if (peerAddress.isNull() || !peerPort) { + emit warningMessage(tr("Failed to extract peer info (address, port)")); + return; + } + + const auto client = std::find_if(knownClients.begin(), knownClients.end(), + [&](const std::unique_ptr &connection){ + return connection->peerAddress() == peerAddress + && connection->peerPort() == peerPort; + }); + //! [4] + + //! [5] + if (client == knownClients.end()) + return handleNewConnection(peerAddress, peerPort, dgram); + //! [5] + + //! [6] + if ((*client)->isConnectionEncrypted()) { + decryptDatagram(client->get(), dgram); + if ((*client)->dtlsError() == QDtlsError::RemoteClosedConnectionError) + knownClients.erase(client); + return; + } + //! [6] + + //! [7] + doHandshake(client->get(), dgram); + //! [7] +} + +//! [13] +void DtlsServer::pskRequired(QSslPreSharedKeyAuthenticator *auth) +{ + Q_ASSERT(auth); + + emit infoMessage(tr("PSK callback, received a client's identity: '%1'") + .arg(QString::fromLatin1(auth->identity()))); + auth->setPreSharedKey(QByteArrayLiteral("\x1a\x2b\x3c\x4d\x5e\x6f")); +} +//! [13] + +//! [8] +void DtlsServer::handleNewConnection(const QHostAddress &peerAddress, + quint16 peerPort, const QByteArray &clientHello) +{ + if (!listening) + return; + + const QString peerInfo = peer_info(peerAddress, peerPort); + if (cookieSender.verifyClient(&serverSocket, clientHello, peerAddress, peerPort)) { + emit infoMessage(peerInfo + tr(": verified, starting a handshake")); + //! [8] + //! [9] + std::unique_ptr newConnection{new QDtls{QSslSocket::SslServerMode}}; + newConnection->setDtlsConfiguration(serverConfiguration); + newConnection->setPeer(peerAddress, peerPort); + newConnection->connect(newConnection.get(), &QDtls::pskRequired, + this, &DtlsServer::pskRequired); + knownClients.push_back(std::move(newConnection)); + doHandshake(knownClients.back().get(), clientHello); + //! [9] + } else if (cookieSender.dtlsError() != QDtlsError::NoError) { + emit errorMessage(tr("DTLS error: ") + cookieSender.dtlsErrorString()); + } else { + emit infoMessage(peerInfo + tr(": not verified yet")); + } +} + +//! [11] +void DtlsServer::doHandshake(QDtls *newConnection, const QByteArray &clientHello) +{ + const bool result = newConnection->doHandshake(&serverSocket, clientHello); + if (!result) { + emit errorMessage(newConnection->dtlsErrorString()); + return; + } + + const QString peerInfo = peer_info(newConnection->peerAddress(), + newConnection->peerPort()); + switch (newConnection->handshakeState()) { + case QDtls::HandshakeInProgress: + emit infoMessage(peerInfo + tr(": handshake is in progress ...")); + break; + case QDtls::HandshakeComplete: + emit infoMessage(tr("Connection with %1 encrypted. %2") + .arg(peerInfo, connection_info(newConnection))); + break; + default: + Q_UNREACHABLE(); + } +} +//! [11] + +//! [12] +void DtlsServer::decryptDatagram(QDtls *connection, const QByteArray &clientMessage) +{ + Q_ASSERT(connection->isConnectionEncrypted()); + + const QString peerInfo = peer_info(connection->peerAddress(), connection->peerPort()); + const QByteArray dgram = connection->decryptDatagram(&serverSocket, clientMessage); + if (dgram.size()) { + emit datagramReceived(peerInfo, clientMessage, dgram); + connection->writeDatagramEncrypted(&serverSocket, tr("to %1: ACK").arg(peerInfo).toLatin1()); + } else if (connection->dtlsError() == QDtlsError::NoError) { + emit warningMessage(peerInfo + ": " + tr("0 byte dgram, could be a re-connect attempt?")); + } else { + emit errorMessage(peerInfo + ": " + connection->dtlsErrorString()); + } +} +//! [12] + +//! [14] +void DtlsServer::shutdown() +{ + for (const auto &connection : std::exchange(knownClients, {})) + connection->shutdown(&serverSocket); + + serverSocket.close(); +} +//! [14] diff --git a/src/dtlsserver.h b/src/dtlsserver.h new file mode 100644 index 00000000..64a4db52 --- /dev/null +++ b/src/dtlsserver.h @@ -0,0 +1,60 @@ + +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +#ifndef SERVER_H +#define SERVER_H + +#include +#include + +#include +#include + +//! [0] +class DtlsServer : public QObject +{ + Q_OBJECT + +public: + DtlsServer(); + ~DtlsServer(); + + bool listen(const QHostAddress &address, quint16 port); + bool isListening() const; + void close(); + + QUdpSocket serverSocket; + + +signals: + void errorMessage(const QString &message); + void warningMessage(const QString &message); + void infoMessage(const QString &message); + + void datagramReceived(const QString &peerInfo, const QByteArray &cipherText, + const QByteArray &plainText); + +private slots: + void readyRead(); + void pskRequired(QSslPreSharedKeyAuthenticator *auth); + +private: + void handleNewConnection(const QHostAddress &peerAddress, quint16 peerPort, + const QByteArray &clientHello); + + void doHandshake(QDtls *newConnection, const QByteArray &clientHello); + void decryptDatagram(QDtls *connection, const QByteArray &clientMessage); + void shutdown(); + + bool listening = false; + + QSslConfiguration serverConfiguration; + QDtlsClientVerifier cookieSender; + std::vector> knownClients; + + Q_DISABLE_COPY(DtlsServer) +}; +//! [0] + +#endif // SERVER_H + diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 29bee876..6e0c73f1 100755 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -332,7 +332,7 @@ MainWindow::MainWindow(QWidget *parent) : connect(IPmodeButton, SIGNAL(clicked()), this, SLOT(toggleIPv4_IPv6())); - + DTLSServerStatus(); UDPServerStatus(); TCPServerStatus(); SSLServerStatus(); diff --git a/src/packetnetwork.cpp b/src/packetnetwork.cpp index e535bbb9..3d07d1ac 100755 --- a/src/packetnetwork.cpp +++ b/src/packetnetwork.cpp @@ -27,6 +27,8 @@ #include "association.h" #include #include "dtlsthread.h" +#include "dtlsserver.h" + @@ -260,7 +262,6 @@ void PacketNetwork::init() int tcpPort = 0; int sslPort = 0; - //settings.setValue("dtlsPort", "12346"); dtlsPortList = Settings::portsToIntList(settings.value("dtlsPort", "0").toString()); udpPortList = Settings::portsToIntList(settings.value("udpPort", "0").toString()); tcpPortList = Settings::portsToIntList(settings.value("tcpPort", "0").toString()); @@ -284,16 +285,19 @@ void PacketNetwork::init() QUdpSocket *udpSocket, *dtlsSocket; ThreadedTCPServer *ssl, *tcp; + + + + foreach (dtlsPort, dtlsPortList) { /////////////////////////////////after adding the DtlsServer class////////////////// - //DtlsServer dtlsServer; - //bool bindResult = dtlsServer.serverSocket.listen(IPV4_OR_IPV6, dtlsPort); - - dtlsSocket = new QUdpSocket(this); + bool bindResult = dtlsServer.listen(IPV4_OR_IPV6, dtlsPort); + dtlsSocket = &(dtlsServer.serverSocket); +// dtlsSocket = new QUdpSocket(this); - bool bindResult = dtlsSocket->bind( - IPV4_OR_IPV6 - , dtlsPort); +// bool bindResult = dtlsSocket->bind( +// IPV4_OR_IPV6 +// , dtlsPort); dtlsSocket->setSocketOption(QAbstractSocket::MulticastTtlOption, 128); @@ -319,10 +323,11 @@ void PacketNetwork::init() } if(bindResult) { - dtlsServers.append(dtlsSocket); +// dtlsServers.append(dtlsSocket); ////////////////////////////////////////after adding the DtlsServer class////////////////// - - //dtlsServers.append(dtlsServer.serverSocket); +// QUdpSocket dtlsSocket = dtlsServer.serverSocket; +// dtlsServers.append(&dtlsSocket); + dtlsServers.append(dtlsSocket); } } @@ -447,12 +452,12 @@ void PacketNetwork::init() if (activateDTLS) { foreach (dtlsSocket, dtlsServers) { /////////////////////////////////after adding the DtlsServer class////////////////// -// -// connect(&server, &DtlsServer::errorMessage, this, &MainWindow::addErrorMessage); -// connect(&server, &DtlsServer::warningMessage, this, &MainWindow::addWarningMessage); -// connect(&server, &DtlsServer::infoMessage, this, &MainWindow::addInfoMessage); -// connect(&server, &DtlsServer::datagramReceived, this, &MainWindow::addClientMessage); -// + +// connect(&dtlsSocket, &DtlsServer::errorMessage, this, &MainWindow::addErrorMessage); +// connect(&dtlsSocket, &DtlsServer::warningMessage, this, &MainWindow::addWarningMessage); +// connect(&dtlsSocket, &DtlsServer::infoMessage, this, &MainWindow::addInfoMessage); +// connect(&dtlsSocket, &DtlsServer::datagramReceived, this, &MainWindow::addClientMessage); + QDEBUG() << "signal/slot datagram connect: " << connect(dtlsSocket, SIGNAL(readyRead()), this, SLOT(readPendingDatagrams())); } @@ -514,13 +519,17 @@ QList PacketNetwork::getDTLSPortsBound() { QList pList; pList.clear(); - QUdpSocket * dtls; - foreach (dtls, dtlsServers) { - if(dtls->BoundState == QAbstractSocket::BoundState) { - if(dtls->localAddress().isMulticast()) { - QDEBUG() << "This udp address is multicast"; + //DtlsServer dtlsServer; + QUdpSocket * dtlsServer; + //= &(dtlsServer.serverSocket); + foreach (dtlsServer, dtlsServers) { + if(dtlsServer->BoundState == QAbstractSocket::BoundState) { +// if(dtls->localAddress().isMulticast()) { +// QDEBUG() << "This udp address is multicast"; +// } + if(dtlsServer){ + pList.append(dtlsServer->localPort()); } - pList.append(dtls->localPort()); } } return pList; diff --git a/src/packetnetwork.h b/src/packetnetwork.h index 93144013..ee37e092 100755 --- a/src/packetnetwork.h +++ b/src/packetnetwork.h @@ -33,6 +33,8 @@ #include #include #include "dtlsthread.h" +#include "dtlsserver.h" + @@ -40,6 +42,8 @@ class PacketNetwork : public QObject { Q_OBJECT public: + QList dtlsServers; + DtlsServer dtlsServer; QString keyPath; QString certPath; explicit PacketNetwork(QObject *parent = nullptr); @@ -141,7 +145,7 @@ private slots: QList tcpServers; QList sslServers; QList udpServers; - QList dtlsServers; + }; From 9672ad70a28dedd20a8e8a78479be44451aa0882 Mon Sep 17 00:00:00 2001 From: israel Date: Mon, 4 Dec 2023 22:19:13 +0200 Subject: [PATCH 51/79] added signals and slots in order to present massage that recived from the client, using DtlsServer::receivedDatagram function --- src/dtlsserver.cpp | 20 ++++++++++++++++++++ src/dtlsserver.h | 8 ++++++-- src/packetnetwork.cpp | 3 +++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/dtlsserver.cpp b/src/dtlsserver.cpp index 7043427d..1c0d82ba 100644 --- a/src/dtlsserver.cpp +++ b/src/dtlsserver.cpp @@ -243,3 +243,23 @@ void DtlsServer::shutdown() serverSocket.close(); } //! [14] + +//this function using for creation packet that can be sent to packetReceivedECHO +// void datagramReceived(const QString &peerInfo, const QByteArray &cipherText, const QByteArray &plainText); +// void receivedDatagram(QString & peerInfo, QByteArray &clientMessage, QByteArray dgram); + +void DtlsServer::receivedDatagram(const QString& peerInfo, const QByteArray &clientMessage, const QByteArray& dgram){ + //recievedMassage = QString::fromUtf8(plainText); + Packet recPacket; +// recPacket.init(); +// recPacket.fromIP = dtlsAssociation->crypto.peerAddress().toString(); +// recPacket.fromPort = dtlsAssociation->crypto.peerPort(); +// QString massageFromTheOtherPeer = QString::fromUtf8(plainText); +// recPacket.hexString = massageFromTheOtherPeer; +// recPacket.toIP = peerInfo; +// recPacket.port = peerInfo; +// recPacket.errorString = "none"; +// recPacket.tcpOrUdp = "DTLS"; + + emit packetReceived(recPacket); +} diff --git a/src/dtlsserver.h b/src/dtlsserver.h index 64a4db52..f7161a4b 100644 --- a/src/dtlsserver.h +++ b/src/dtlsserver.h @@ -9,6 +9,7 @@ #include #include +#include "packet.h" //! [0] class DtlsServer : public QObject @@ -27,17 +28,20 @@ class DtlsServer : public QObject signals: + void packetReceived(Packet); void errorMessage(const QString &message); void warningMessage(const QString &message); void infoMessage(const QString &message); - void datagramReceived(const QString &peerInfo, const QByteArray &cipherText, - const QByteArray &plainText); + void datagramReceived(const QString &peerInfo, const QByteArray &cipherText, const QByteArray &plainText); private slots: void readyRead(); void pskRequired(QSslPreSharedKeyAuthenticator *auth); +public slots: + void receivedDatagram(const QString& peerInfo, const QByteArray &clientMessage, const QByteArray& dgram); + private: void handleNewConnection(const QHostAddress &peerAddress, quint16 peerPort, const QByteArray &clientHello); diff --git a/src/packetnetwork.cpp b/src/packetnetwork.cpp index 3d07d1ac..2801861d 100755 --- a/src/packetnetwork.cpp +++ b/src/packetnetwork.cpp @@ -292,6 +292,9 @@ void PacketNetwork::init() foreach (dtlsPort, dtlsPortList) { /////////////////////////////////after adding the DtlsServer class////////////////// bool bindResult = dtlsServer.listen(IPV4_OR_IPV6, dtlsPort); + connect(&dtlsServer, SIGNAL(packetReceived(Packet)), this, SLOT(packetReceivedECHO(Packet))); + connect(&dtlsServer,&DtlsServer::datagramReceived,&dtlsServer,&DtlsServer::receivedDatagram); + dtlsSocket = &(dtlsServer.serverSocket); // dtlsSocket = new QUdpSocket(this); From 003e18b09b305fe924c6c667e318c21b12dcba73 Mon Sep 17 00:00:00 2001 From: israel Date: Mon, 4 Dec 2023 22:49:43 +0200 Subject: [PATCH 52/79] the server can present packets he recieves, but he prints them multiple times to the trafficLog --- src/dtlsserver.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/dtlsserver.cpp b/src/dtlsserver.cpp index 1c0d82ba..a73d6d24 100644 --- a/src/dtlsserver.cpp +++ b/src/dtlsserver.cpp @@ -251,6 +251,18 @@ void DtlsServer::shutdown() void DtlsServer::receivedDatagram(const QString& peerInfo, const QByteArray &clientMessage, const QByteArray& dgram){ //recievedMassage = QString::fromUtf8(plainText); Packet recPacket; + recPacket.init(); + recPacket.fromIP = "You"; + recPacket.fromPort = serverSocket.localPort(); + QStringList info = peerInfo.split(":"); + recPacket.toIP = info[0].remove(0, 1); + recPacket.port = info[1].toUInt(); + QString massageFromTheOtherPeer = QString::fromUtf8(dgram); + recPacket.hexString = massageFromTheOtherPeer; + recPacket.errorString = "none"; + recPacket.tcpOrUdp = "DTLS"; + + // recPacket.init(); // recPacket.fromIP = dtlsAssociation->crypto.peerAddress().toString(); // recPacket.fromPort = dtlsAssociation->crypto.peerPort(); From 4113fc2008c02319f12f89c045b9cfa720b1a492 Mon Sep 17 00:00:00 2001 From: israel Date: Thu, 7 Dec 2023 13:40:54 +0200 Subject: [PATCH 53/79] the server can present the massage he recieved --- src/association.cpp | 2 ++ src/dtlsserver.cpp | 21 +++++++++++++++++---- src/dtlsserver.h | 8 +++++--- src/dtlsthread.cpp | 11 ++++++++++- src/packetnetwork.cpp | 40 +++++++++++++++++++++++++++++++--------- 5 files changed, 65 insertions(+), 17 deletions(-) diff --git a/src/association.cpp b/src/association.cpp index 40413631..0ace7bcb 100644 --- a/src/association.cpp +++ b/src/association.cpp @@ -35,6 +35,8 @@ DtlsAssociation::DtlsAssociation(const QHostAddress &address, quint16 port, configuration.setPeerVerifyMode(QSslSocket::VerifyNone); crypto.setPeer(address, port); crypto.setDtlsConfiguration(configuration); + //connect(&crypto, &QDtls::handshakeTimeout, this, &DtlsAssociation::handshakeTimeout); + connect(&crypto, &QDtls::pskRequired, this, &DtlsAssociation::pskRequired); //! [3] socket.connectToHost(address.toString(), port); diff --git a/src/dtlsserver.cpp b/src/dtlsserver.cpp index a73d6d24..65538a2b 100644 --- a/src/dtlsserver.cpp +++ b/src/dtlsserver.cpp @@ -224,8 +224,14 @@ void DtlsServer::decryptDatagram(QDtls *connection, const QByteArray &clientMess const QString peerInfo = peer_info(connection->peerAddress(), connection->peerPort()); const QByteArray dgram = connection->decryptDatagram(&serverSocket, clientMessage); if (dgram.size()) { - emit datagramReceived(peerInfo, clientMessage, dgram); - connection->writeDatagramEncrypted(&serverSocket, tr("to %1: ACK").arg(peerInfo).toLatin1()); + //if(QAbstractSocket::) + if(connection->writeDatagramEncrypted(&serverSocket, tr("to %1: ACK").arg(peerInfo).toLatin1())){ + //if (connection->sslMode() == QSslSocket::SslServerMode) { + emit serverDatagramReceived(peerInfo, clientMessage, dgram); + //serverSocket.waitForReadyRead(); + //} + + } } else if (connection->dtlsError() == QDtlsError::NoError) { emit warningMessage(peerInfo + ": " + tr("0 byte dgram, could be a re-connect attempt?")); } else { @@ -248,7 +254,7 @@ void DtlsServer::shutdown() // void datagramReceived(const QString &peerInfo, const QByteArray &cipherText, const QByteArray &plainText); // void receivedDatagram(QString & peerInfo, QByteArray &clientMessage, QByteArray dgram); -void DtlsServer::receivedDatagram(const QString& peerInfo, const QByteArray &clientMessage, const QByteArray& dgram){ +void DtlsServer::serverReceivedDatagram(const QString& peerInfo, const QByteArray &clientMessage, const QByteArray& dgram){ //recievedMassage = QString::fromUtf8(plainText); Packet recPacket; recPacket.init(); @@ -273,5 +279,12 @@ void DtlsServer::receivedDatagram(const QString& peerInfo, const QByteArray &cli // recPacket.errorString = "none"; // recPacket.tcpOrUdp = "DTLS"; - emit packetReceived(recPacket); + emit serverPacketReceived(recPacket); + + //emit packetSent + + //if (dtlsObject.handshakeState() == QDtls::HandshakeComplete) { + + //emit serverPacketReceived(recPacket); + } diff --git a/src/dtlsserver.h b/src/dtlsserver.h index f7161a4b..bd86dbf7 100644 --- a/src/dtlsserver.h +++ b/src/dtlsserver.h @@ -20,6 +20,8 @@ class DtlsServer : public QObject DtlsServer(); ~DtlsServer(); + + bool listen(const QHostAddress &address, quint16 port); bool isListening() const; void close(); @@ -28,19 +30,19 @@ class DtlsServer : public QObject signals: - void packetReceived(Packet); + void serverPacketReceived(Packet); void errorMessage(const QString &message); void warningMessage(const QString &message); void infoMessage(const QString &message); - void datagramReceived(const QString &peerInfo, const QByteArray &cipherText, const QByteArray &plainText); + void serverDatagramReceived(const QString &peerInfo, const QByteArray &cipherText, const QByteArray &plainText); private slots: void readyRead(); void pskRequired(QSslPreSharedKeyAuthenticator *auth); public slots: - void receivedDatagram(const QString& peerInfo, const QByteArray &clientMessage, const QByteArray& dgram); + void serverReceivedDatagram(const QString& peerInfo, const QByteArray &clientMessage, const QByteArray& dgram); private: void handleNewConnection(const QHostAddress &peerAddress, quint16 peerPort, diff --git a/src/dtlsthread.cpp b/src/dtlsthread.cpp index c0d3648a..5bcb1e7a 100644 --- a/src/dtlsthread.cpp +++ b/src/dtlsthread.cpp @@ -21,6 +21,7 @@ void Dtlsthread::run() handShakeDone = false; dtlsAssociation = initDtlsAssociation(); connect(dtlsAssociation, &DtlsAssociation::handShakeComplited,this, &Dtlsthread::handShakeComplited); + dtlsAssociation->startHandshake(); writeMassage(sendpacket, dtlsAssociation); persistentConnectionLoop(); @@ -248,13 +249,16 @@ void Dtlsthread::persistentConnectionLoop() // QDEBUG() << "Persistent connection. Loop and wait."; // continue; // } - } // end while connected + } // end while connected if (closeRequest) { clientConnection->close(); clientConnection->waitForDisconnected(100); + //quit(); } + insidePersistent = false; + } void Dtlsthread::receivedDatagram(QByteArray plainText){ recievedMassage = QString::fromUtf8(plainText); @@ -305,6 +309,11 @@ void Dtlsthread::sendPersistant(Packet sendpacket) void Dtlsthread::onTimeout(){ closeRequest = true; timer->stop(); + //dtlsAssociation->crypto.abortHandshake(&(dtlsAssociation->socket)); + dtlsAssociation->socket.close(); + dtlsAssociation->socket.waitForDisconnected(100); + //quit(); + } DtlsAssociation* Dtlsthread::initDtlsAssociation(){ diff --git a/src/packetnetwork.cpp b/src/packetnetwork.cpp index 2801861d..d3e4de84 100755 --- a/src/packetnetwork.cpp +++ b/src/packetnetwork.cpp @@ -288,12 +288,12 @@ void PacketNetwork::init() - +// connect(&dtlsServer, SIGNAL(serverPacketReceived(Packet)), this, SLOT(packetReceivedECHO(Packet)),Qt::UniqueConnection); +// connect(&dtlsServer,&DtlsServer::serverDatagramReceived,&dtlsServer,&DtlsServer::serverReceivedDatagram,Qt::UniqueConnection); foreach (dtlsPort, dtlsPortList) { /////////////////////////////////after adding the DtlsServer class////////////////// bool bindResult = dtlsServer.listen(IPV4_OR_IPV6, dtlsPort); - connect(&dtlsServer, SIGNAL(packetReceived(Packet)), this, SLOT(packetReceivedECHO(Packet))); - connect(&dtlsServer,&DtlsServer::datagramReceived,&dtlsServer,&DtlsServer::receivedDatagram); + dtlsSocket = &(dtlsServer.serverSocket); // dtlsSocket = new QUdpSocket(this); @@ -454,6 +454,21 @@ void PacketNetwork::init() if (activateDTLS) { foreach (dtlsSocket, dtlsServers) { + connect(&dtlsServer, SIGNAL(serverPacketReceived(Packet)), this, SLOT(packetReceivedECHO(Packet)),Qt::UniqueConnection); + connect(&dtlsServer,&DtlsServer::serverDatagramReceived,&dtlsServer,&DtlsServer::serverReceivedDatagram,Qt::UniqueConnection); +// QUdpSocket *udpSocket, *dtlsSocket; +// ThreadedTCPServer *ssl, *tcp; + + + +// connect(&dtlsServer, SIGNAL(serverPacketReceived(Packet)), this, SLOT(packetReceivedECHO(Packet))); +// connect(&dtlsServer,&DtlsServer::serverDatagramReceived,&dtlsServer,&DtlsServer::serverReceivedDatagram); +// foreach (dtlsPort, dtlsPortList) { +// /////////////////////////////////after adding the DtlsServer class////////////////// +// bool bindResult = dtlsServer.listen(IPV4_OR_IPV6, dtlsPort); + + +// dtlsSocket = &(dtlsServer.serverSocket); /////////////////////////////////after adding the DtlsServer class////////////////// // connect(&dtlsSocket, &DtlsServer::errorMessage, this, &MainWindow::addErrorMessage); @@ -461,27 +476,30 @@ void PacketNetwork::init() // connect(&dtlsSocket, &DtlsServer::infoMessage, this, &MainWindow::addInfoMessage); // connect(&dtlsSocket, &DtlsServer::datagramReceived, this, &MainWindow::addClientMessage); - QDEBUG() << "signal/slot datagram connect: " << connect(dtlsSocket, SIGNAL(readyRead()), - this, SLOT(readPendingDatagrams())); +// QDEBUG() << "signal/slot datagram connect: " << connect(dtlsSocket, SIGNAL(readyRead()), +// this, SLOT(readPendingDatagrams())); + } } else { QDEBUG() << "udp server disable"; - foreach (udpSocket, udpServers) { - udpSocket->close(); + foreach (dtlsSocket, udpServers) { + dtlsSocket->close(); } - udpServers.clear(); + dtlsServers.clear(); } if (activateUDP) { + foreach (udpSocket, udpServers) { QDEBUG() << "signal/slot datagram connect: " << connect(udpSocket, SIGNAL(readyRead()), this, SLOT(readPendingDatagrams())); } + } else { QDEBUG() << "udp server disable"; foreach (udpSocket, udpServers) { @@ -806,6 +824,7 @@ void PacketNetwork::readPendingDatagrams() } + QString PacketNetwork::debugQByteArray(QByteArray debugArray) { QString outString = ""; @@ -982,7 +1001,10 @@ void PacketNetwork::packetToSend(Packet sendpacket) QTimer* timer = new QTimer(this); thread->timer = timer; connect(timer, SIGNAL(timeout()), thread, SLOT(onTimeout())); - timer->start(1000); + timer->start(500); + } + if(thread->isFinished()){ + return; } dtlsthreadList.append(thread); } From e2ddbd7a359894abad9804c7515a37655dc86e70 Mon Sep 17 00:00:00 2001 From: israel Date: Sun, 10 Dec 2023 14:20:30 +0200 Subject: [PATCH 54/79] if the client can't do handshake the thread is killed --- src/association.cpp | 8 +++++++- src/association.h | 2 ++ src/dtlsthread.cpp | 10 ++++++++-- src/dtlsthread.h | 1 + src/packetnetwork.cpp | 6 +++--- 5 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/association.cpp b/src/association.cpp index 0ace7bcb..37081c4f 100644 --- a/src/association.cpp +++ b/src/association.cpp @@ -10,6 +10,10 @@ DtlsAssociation::DtlsAssociation(const QHostAddress &address, quint16 port, crypto(QSslSocket::SslClientMode) { + + + + QFile certFile(cmdComponents[4]);//4 if(!certFile.open(QIODevice::ReadOnly)){ return; @@ -70,8 +74,10 @@ void DtlsAssociation::startHandshake() else{ while(true){ socket.waitForReadyRead(); - if(crypto.isConnectionEncrypted()){ + if(crypto.isConnectionEncrypted() || closeRequest){ + break; + } } emit infoMessage(tr("%1: starting a handshake").arg(name)); diff --git a/src/association.h b/src/association.h index f1521b53..94e6c3f6 100644 --- a/src/association.h +++ b/src/association.h @@ -25,6 +25,8 @@ class DtlsAssociation : public QObject QString name; Packet packetToSend; + bool closeRequest; + signals: void errorMessage(const QString &message); void warningMessage(const QString &message); diff --git a/src/dtlsthread.cpp b/src/dtlsthread.cpp index 5bcb1e7a..86f9cb1a 100644 --- a/src/dtlsthread.cpp +++ b/src/dtlsthread.cpp @@ -17,9 +17,12 @@ Dtlsthread::~Dtlsthread() { void Dtlsthread::run() { + closeRequest = false; handShakeDone = false; dtlsAssociation = initDtlsAssociation(); + dtlsAssociation->closeRequest = false; + //dtlsAssociation->deleteLater(); connect(dtlsAssociation, &DtlsAssociation::handShakeComplited,this, &Dtlsthread::handShakeComplited); dtlsAssociation->startHandshake(); @@ -307,11 +310,14 @@ void Dtlsthread::sendPersistant(Packet sendpacket) } void Dtlsthread::onTimeout(){ + dtlsAssociation->closeRequest = true; closeRequest = true; timer->stop(); //dtlsAssociation->crypto.abortHandshake(&(dtlsAssociation->socket)); - dtlsAssociation->socket.close(); - dtlsAssociation->socket.waitForDisconnected(100); + //dtlsAssociation->socket.close(); + //dtlsAssociation->deleteLater(); + + //dtlsAssociation->socket.waitForDisconnected(100); //quit(); } diff --git a/src/dtlsthread.h b/src/dtlsthread.h index a4a9bed1..202b8046 100644 --- a/src/dtlsthread.h +++ b/src/dtlsthread.h @@ -26,6 +26,7 @@ class Dtlsthread : public QThread QString recievedMassage; Packet sendpacket; + bool closeRequest; bool handShakeDone; bool insidePersistent; diff --git a/src/packetnetwork.cpp b/src/packetnetwork.cpp index d3e4de84..02bb5387 100755 --- a/src/packetnetwork.cpp +++ b/src/packetnetwork.cpp @@ -1003,9 +1003,9 @@ void PacketNetwork::packetToSend(Packet sendpacket) connect(timer, SIGNAL(timeout()), thread, SLOT(onTimeout())); timer->start(500); } - if(thread->isFinished()){ - return; - } +// if(thread->isFinished()){ +// return; +// } dtlsthreadList.append(thread); } From b05f30a4dc255d0bf2f0f11671e65891ab8c18f2 Mon Sep 17 00:00:00 2001 From: israel Date: Sun, 10 Dec 2023 14:59:10 +0200 Subject: [PATCH 55/79] the server can present correct details about the recieved packet --- src/dtlsserver.cpp | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/src/dtlsserver.cpp b/src/dtlsserver.cpp index 65538a2b..3114c168 100644 --- a/src/dtlsserver.cpp +++ b/src/dtlsserver.cpp @@ -259,32 +259,26 @@ void DtlsServer::serverReceivedDatagram(const QString& peerInfo, const QByteArra Packet recPacket; recPacket.init(); recPacket.fromIP = "You"; - recPacket.fromPort = serverSocket.localPort(); QStringList info = peerInfo.split(":"); recPacket.toIP = info[0].remove(0, 1); - recPacket.port = info[1].toUInt(); + recPacket.fromPort = info[1].remove(info[1].length() - 1, 1).toUInt(); + recPacket.port = serverSocket.localPort(); QString massageFromTheOtherPeer = QString::fromUtf8(dgram); recPacket.hexString = massageFromTheOtherPeer; recPacket.errorString = "none"; recPacket.tcpOrUdp = "DTLS"; -// recPacket.init(); -// recPacket.fromIP = dtlsAssociation->crypto.peerAddress().toString(); -// recPacket.fromPort = dtlsAssociation->crypto.peerPort(); -// QString massageFromTheOtherPeer = QString::fromUtf8(plainText); -// recPacket.hexString = massageFromTheOtherPeer; -// recPacket.toIP = peerInfo; -// recPacket.port = peerInfo; -// recPacket.errorString = "none"; -// recPacket.tcpOrUdp = "DTLS"; + // recPacket.init(); + // recPacket.fromIP = dtlsAssociation->crypto.peerAddress().toString(); + // recPacket.fromPort = dtlsAssociation->crypto.peerPort(); + // QString massageFromTheOtherPeer = QString::fromUtf8(plainText); + // recPacket.hexString = massageFromTheOtherPeer; + // recPacket.toIP = peerInfo; + // recPacket.port = peerInfo; + // recPacket.errorString = "none"; + // recPacket.tcpOrUdp = "DTLS"; emit serverPacketReceived(recPacket); - //emit packetSent - - //if (dtlsObject.handshakeState() == QDtls::HandshakeComplete) { - - //emit serverPacketReceived(recPacket); - } From 3d4b46576a904d8d49e1e1b92391110b3662dbbd Mon Sep 17 00:00:00 2001 From: israel Date: Mon, 11 Dec 2023 11:14:56 +0200 Subject: [PATCH 56/79] client and server are working at persistent mode and also at non-persistent mode --- src/dtlsserver.cpp | 79 ++++++++++++++++++++++++++++++------------- src/dtlsserver.h | 10 ++++-- src/packetnetwork.cpp | 4 ++- 3 files changed, 67 insertions(+), 26 deletions(-) diff --git a/src/dtlsserver.cpp b/src/dtlsserver.cpp index 3114c168..4100baba 100644 --- a/src/dtlsserver.cpp +++ b/src/dtlsserver.cpp @@ -13,6 +13,7 @@ QString peer_info(const QHostAddress &address, quint16 port) return info.arg(address.toString()).arg(port); } + QString connection_info(QDtls *connection) { QString info(DtlsServer::tr("Session cipher: ")); @@ -223,14 +224,18 @@ void DtlsServer::decryptDatagram(QDtls *connection, const QByteArray &clientMess const QString peerInfo = peer_info(connection->peerAddress(), connection->peerPort()); const QByteArray dgram = connection->decryptDatagram(&serverSocket, clientMessage); + if (dgram.size()) { - //if(QAbstractSocket::) - if(connection->writeDatagramEncrypted(&serverSocket, tr("to %1: ACK").arg(peerInfo).toLatin1())){ - //if (connection->sslMode() == QSslSocket::SslServerMode) { - emit serverDatagramReceived(peerInfo, clientMessage, dgram); - //serverSocket.waitForReadyRead(); - //} + //the vector content: createInfoVect(const QHostAddress &fromAddress, quint16 fromPort, const QHostAddress &toAddress, quint16 toPort) + //TODO: insert the vector error massage + std::vector recievedPacketInfo = createInfoVect(connection->peerAddress(), connection->peerPort(), serverSocket.localAddress(), serverSocket.localPort()); + Packet recivedPacket = createPacket(recievedPacketInfo, clientMessage, dgram); + emit serverPacketReceived(recivedPacket); + if(connection->writeDatagramEncrypted(&serverSocket, tr("to %1: ACK").arg(peerInfo).toLatin1())){ + std::vector sentPacketInfo = createInfoVect(serverSocket.localAddress(), serverSocket.localPort(), connection->peerAddress(), connection->peerPort()); + Packet sentPacket = createPacket(sentPacketInfo, clientMessage, dgram); + emit serverPacketSent(sentPacket); } } else if (connection->dtlsError() == QDtlsError::NoError) { emit warningMessage(peerInfo + ": " + tr("0 byte dgram, could be a re-connect attempt?")); @@ -254,31 +259,59 @@ void DtlsServer::shutdown() // void datagramReceived(const QString &peerInfo, const QByteArray &cipherText, const QByteArray &plainText); // void receivedDatagram(QString & peerInfo, QByteArray &clientMessage, QByteArray dgram); -void DtlsServer::serverReceivedDatagram(const QString& peerInfo, const QByteArray &clientMessage, const QByteArray& dgram){ - //recievedMassage = QString::fromUtf8(plainText); +Packet DtlsServer::createPacket(const std::vector& packetInfo, const QByteArray &clientMessage, const QByteArray& dgram){ + Packet recPacket; recPacket.init(); - recPacket.fromIP = "You"; - QStringList info = peerInfo.split(":"); - recPacket.toIP = info[0].remove(0, 1); - recPacket.fromPort = info[1].remove(info[1].length() - 1, 1).toUInt(); - recPacket.port = serverSocket.localPort(); + recPacket.fromIP = packetInfo[0]; + recPacket.fromPort = packetInfo[1].toUInt(); + recPacket.toIP = packetInfo[2]; + recPacket.port = packetInfo[3].toUInt(); QString massageFromTheOtherPeer = QString::fromUtf8(dgram); recPacket.hexString = massageFromTheOtherPeer; recPacket.errorString = "none"; recPacket.tcpOrUdp = "DTLS"; + if((packetInfo[0] == "0.0.0.0") || (packetInfo[0] == "127.0.0.1")){ + recPacket.fromIP = "You"; + } + if((packetInfo[2] == "0.0.0.0") || (packetInfo[2] == "127.0.0.1")){ + recPacket.toIP = "You"; + } - // recPacket.init(); - // recPacket.fromIP = dtlsAssociation->crypto.peerAddress().toString(); - // recPacket.fromPort = dtlsAssociation->crypto.peerPort(); - // QString massageFromTheOtherPeer = QString::fromUtf8(plainText); - // recPacket.hexString = massageFromTheOtherPeer; - // recPacket.toIP = peerInfo; - // recPacket.port = peerInfo; - // recPacket.errorString = "none"; - // recPacket.tcpOrUdp = "DTLS"; + return recPacket; - emit serverPacketReceived(recPacket); } + +std::vector DtlsServer::createInfoVect(const QHostAddress &fromAddress, quint16 fromPort, const QHostAddress &toAddress, quint16 toPort){ + std::vector infoVect; + infoVect.push_back(fromAddress.toString()); + infoVect.push_back(QString::number(fromPort)); + infoVect.push_back(toAddress.toString()); + infoVect.push_back(QString::number(toPort)); + return infoVect; + +} + +//void DtlsServer::serverSentDatagram(const QString& peerInfo, const QByteArray &clientMessage, const QByteArray& dgram){ +// Packet recPacket; +// recPacket.init(); +// recPacket.fromIP = "You"; +// QStringList info = peerInfo.split(":"); +// recPacket.toIP = info[0].remove(0, 1); +// recPacket.fromPort = info[1].remove(info[1].length() - 1, 1).toUInt(); +// recPacket.port = serverSocket.localPort(); +// QString massageFromTheOtherPeer = QString::fromUtf8(dgram); +// recPacket.hexString = massageFromTheOtherPeer; +// recPacket.errorString = "none"; +// recPacket.tcpOrUdp = "DTLS"; + +// emit serverPacketSent(recPacket); + +//} + +//std::vector getPacketInfo(const QHostAddress &fromAddress, quint16 fromPort, const QHostAddress &toAddress, quint16 toPort){ +// std::vector infoVect; +// infoVect.push_back(); +//} diff --git a/src/dtlsserver.h b/src/dtlsserver.h index bd86dbf7..2ae10d32 100644 --- a/src/dtlsserver.h +++ b/src/dtlsserver.h @@ -25,24 +25,30 @@ class DtlsServer : public QObject bool listen(const QHostAddress &address, quint16 port); bool isListening() const; void close(); + Packet createPacket(const std::vector& packetInfo, const QByteArray &clientMessage, const QByteArray& dgram); + std::vector createInfoVect(const QHostAddress &fromAddress, quint16 fromPort, const QHostAddress &toAddress, quint16 toPort); + QUdpSocket serverSocket; signals: void serverPacketReceived(Packet); + void serverPacketSent(Packet); + + void errorMessage(const QString &message); void warningMessage(const QString &message); void infoMessage(const QString &message); - void serverDatagramReceived(const QString &peerInfo, const QByteArray &cipherText, const QByteArray &plainText); + //void serverDatagramReceived(const QString &peerInfo, const QByteArray &cipherText, const QByteArray &plainText); private slots: void readyRead(); void pskRequired(QSslPreSharedKeyAuthenticator *auth); public slots: - void serverReceivedDatagram(const QString& peerInfo, const QByteArray &clientMessage, const QByteArray& dgram); + //void serverReceivedDatagram(const QString& peerInfo, const QByteArray &clientMessage, const QByteArray& dgram); private: void handleNewConnection(const QHostAddress &peerAddress, quint16 peerPort, diff --git a/src/packetnetwork.cpp b/src/packetnetwork.cpp index 02bb5387..8fb4ab46 100755 --- a/src/packetnetwork.cpp +++ b/src/packetnetwork.cpp @@ -455,7 +455,9 @@ void PacketNetwork::init() if (activateDTLS) { foreach (dtlsSocket, dtlsServers) { connect(&dtlsServer, SIGNAL(serverPacketReceived(Packet)), this, SLOT(packetReceivedECHO(Packet)),Qt::UniqueConnection); - connect(&dtlsServer,&DtlsServer::serverDatagramReceived,&dtlsServer,&DtlsServer::serverReceivedDatagram,Qt::UniqueConnection); + connect(&dtlsServer, SIGNAL(serverPacketSent(Packet)), this, SLOT(packetSentECHO(Packet)),Qt::UniqueConnection); + + //connect(&dtlsServer,&DtlsServer::serverDatagramReceived,&dtlsServer,&DtlsServer::serverReceivedDatagram,Qt::UniqueConnection); // QUdpSocket *udpSocket, *dtlsSocket; // ThreadedTCPServer *ssl, *tcp; From ba5c215a042ef8fe907dc8f4a5f48d46ad1812db Mon Sep 17 00:00:00 2001 From: israel Date: Mon, 11 Dec 2023 11:21:50 +0200 Subject: [PATCH 57/79] the server ack including the client recieved massage --- src/dtlsserver.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dtlsserver.cpp b/src/dtlsserver.cpp index 4100baba..ef9e79e9 100644 --- a/src/dtlsserver.cpp +++ b/src/dtlsserver.cpp @@ -232,7 +232,8 @@ void DtlsServer::decryptDatagram(QDtls *connection, const QByteArray &clientMess Packet recivedPacket = createPacket(recievedPacketInfo, clientMessage, dgram); emit serverPacketReceived(recivedPacket); - if(connection->writeDatagramEncrypted(&serverSocket, tr("to %1: ACK").arg(peerInfo).toLatin1())){ + //if(connection->writeDatagramEncrypted(&serverSocket, tr("to %1: ACK").arg(peerInfo).toLatin1())){ + if(connection->writeDatagramEncrypted(&serverSocket, tr("to %1: %2").arg(peerInfo, QString::fromUtf8(dgram)).toLatin1())){ std::vector sentPacketInfo = createInfoVect(serverSocket.localAddress(), serverSocket.localPort(), connection->peerAddress(), connection->peerPort()); Packet sentPacket = createPacket(sentPacketInfo, clientMessage, dgram); emit serverPacketSent(sentPacket); From ac0e7099ef575c12071ad14c34b251e39c6a2bb7 Mon Sep 17 00:00:00 2001 From: israel Date: Mon, 11 Dec 2023 12:14:45 +0200 Subject: [PATCH 58/79] fix the impact of changes of the DTLS StatusBar on the UDP StatusBar, and also fix the hex and asckii poblems at the trafficLog (server & client) --- src/dtlsserver.cpp | 5 +---- src/dtlsthread.cpp | 15 ++++++++++----- src/packetnetwork.cpp | 2 +- src/settings.cpp | 3 +-- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/dtlsserver.cpp b/src/dtlsserver.cpp index ef9e79e9..960b92a7 100644 --- a/src/dtlsserver.cpp +++ b/src/dtlsserver.cpp @@ -257,9 +257,6 @@ void DtlsServer::shutdown() //! [14] //this function using for creation packet that can be sent to packetReceivedECHO -// void datagramReceived(const QString &peerInfo, const QByteArray &cipherText, const QByteArray &plainText); -// void receivedDatagram(QString & peerInfo, QByteArray &clientMessage, QByteArray dgram); - Packet DtlsServer::createPacket(const std::vector& packetInfo, const QByteArray &clientMessage, const QByteArray& dgram){ Packet recPacket; @@ -269,7 +266,7 @@ Packet DtlsServer::createPacket(const std::vector& packetInfo, const QB recPacket.toIP = packetInfo[2]; recPacket.port = packetInfo[3].toUInt(); QString massageFromTheOtherPeer = QString::fromUtf8(dgram); - recPacket.hexString = massageFromTheOtherPeer; + recPacket.hexString = recPacket.ASCIITohex(massageFromTheOtherPeer); recPacket.errorString = "none"; recPacket.tcpOrUdp = "DTLS"; diff --git a/src/dtlsthread.cpp b/src/dtlsthread.cpp index 86f9cb1a..57dc2890 100644 --- a/src/dtlsthread.cpp +++ b/src/dtlsthread.cpp @@ -18,7 +18,7 @@ Dtlsthread::~Dtlsthread() { void Dtlsthread::run() { - closeRequest = false; + //closeRequest = false; handShakeDone = false; dtlsAssociation = initDtlsAssociation(); dtlsAssociation->closeRequest = false; @@ -97,7 +97,7 @@ void Dtlsthread::persistentConnectionLoop() } int count = 0; - while (clientConnection->state() == QAbstractSocket::ConnectedState && !closeRequest) { + while (clientConnection->state() == QAbstractSocket::ConnectedState && !(dtlsAssociation->closeRequest)) { insidePersistent = true; @@ -222,7 +222,9 @@ void Dtlsthread::persistentConnectionLoop() emit connectStatus("Reading response"); //tcpPacket.hexString = clientConnection->readAll(); + tcpPacket.hexString = recievedMassage; + //tcpPacket.hexString = tcpPacket.ASCIITohex(recievedMassage); tcpPacket.timestamp = QDateTime::currentDateTime(); tcpPacket.name = QDateTime::currentDateTime().toString(DATETIMEFORMAT); @@ -254,7 +256,7 @@ void Dtlsthread::persistentConnectionLoop() // } } // end while connected - if (closeRequest) { + if (dtlsAssociation->closeRequest) { clientConnection->close(); clientConnection->waitForDisconnected(100); //quit(); @@ -270,7 +272,10 @@ void Dtlsthread::receivedDatagram(QByteArray plainText){ recPacket.fromIP = dtlsAssociation->crypto.peerAddress().toString(); recPacket.fromPort = dtlsAssociation->crypto.peerPort(); QString massageFromTheOtherPeer = QString::fromUtf8(plainText); - recPacket.hexString = massageFromTheOtherPeer; + + recPacket.hexString = recPacket.ASCIITohex(massageFromTheOtherPeer); + + //recPacket.hexString = massageFromTheOtherPeer; recPacket.toIP = dtlsAssociation->socket.localAddress().toString(); recPacket.port = dtlsAssociation->socket.localPort(); recPacket.errorString = "none"; @@ -311,7 +316,7 @@ void Dtlsthread::sendPersistant(Packet sendpacket) void Dtlsthread::onTimeout(){ dtlsAssociation->closeRequest = true; - closeRequest = true; + //closeRequest = true; timer->stop(); //dtlsAssociation->crypto.abortHandshake(&(dtlsAssociation->socket)); //dtlsAssociation->socket.close(); diff --git a/src/packetnetwork.cpp b/src/packetnetwork.cpp index 8fb4ab46..7a215c3e 100755 --- a/src/packetnetwork.cpp +++ b/src/packetnetwork.cpp @@ -485,7 +485,7 @@ void PacketNetwork::init() } else { QDEBUG() << "udp server disable"; - foreach (dtlsSocket, udpServers) { + foreach (dtlsSocket, dtlsServers) { dtlsSocket->close(); } dtlsServers.clear(); diff --git a/src/settings.cpp b/src/settings.cpp index a5ce8590..3caba864 100755 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -216,7 +216,7 @@ Settings::Settings(QWidget *parent, MainWindow* mw) : ui->udpServerEnableCheck->setChecked(settings.value("udpServerEnable", true).toBool()); ui->tcpServerEnableCheck->setChecked(settings.value("tcpServerEnable", true).toBool()); ui->sslServerEnableCheck->setChecked(settings.value("sslServerEnable", true).toBool()); - ui->dtlsServerEnableCheck->setChecked(settings.value("sslServerEnable", true).toBool()); + ui->dtlsServerEnableCheck->setChecked(settings.value("dtlsServerEnable", true).toBool()); ui->serverSnakeOilCheck->setChecked(settings.value("serverSnakeOilCheck", true).toBool()); @@ -410,7 +410,6 @@ void Settings::on_buttonBox_accepted() settings.setValue("responseName", ui->responsePacketBox->currentText().trimmed()); settings.setValue("responseHex", ui->hexResponseEdit->text().trimmed()); - //settings.setValue("dtlsServerEnable", ui->dtlsServerEnableCheck->isChecked()); settings.setValue("udpServerEnable", ui->udpServerEnableCheck->isChecked()); settings.setValue("tcpServerEnable", ui->tcpServerEnableCheck->isChecked()); settings.setValue("sslServerEnable", ui->sslServerEnableCheck->isChecked()); From 80d87818f3c890de18c076ccee8f8be9cb70c83f Mon Sep 17 00:00:00 2001 From: israel Date: Mon, 11 Dec 2023 16:37:44 +0200 Subject: [PATCH 59/79] the server can send a simpleResponse, but the client cant recieve it --- src/dtlsserver.cpp | 136 +++++++++++++++++++++++++++++++++++++++++- src/dtlsserver.h | 13 ++++ src/packetnetwork.cpp | 6 +- src/settings.ui | 6 +- 4 files changed, 153 insertions(+), 8 deletions(-) diff --git a/src/dtlsserver.cpp b/src/dtlsserver.cpp index 960b92a7..e9422311 100644 --- a/src/dtlsserver.cpp +++ b/src/dtlsserver.cpp @@ -142,7 +142,40 @@ void DtlsServer::readyRead() //! [6] if ((*client)->isConnectionEncrypted()) { + //TODO: split into two function, one for decryption and one for writting decryptDatagram(client->get(), dgram); + ///////////////////////////////////////////////////manage send response option////////////////////////////////////////////////////////////// + + + + + QSettings settings(SETTINGSFILE, QSettings::IniFormat); + bool sendResponse = settings.value("sendReponse", false).toBool(); + bool sendSmartResponse = settings.value("sendReponse", false).toBool(); + + + smartData.clear(); + + if (sendSmartResponse) { + QList smartList; + smartList.append(Packet::fetchSmartConfig(1, SETTINGSFILE)); + smartList.append(Packet::fetchSmartConfig(2, SETTINGSFILE)); + smartList.append(Packet::fetchSmartConfig(3, SETTINGSFILE)); + smartList.append(Packet::fetchSmartConfig(4, SETTINGSFILE)); + smartList.append(Packet::fetchSmartConfig(5, SETTINGSFILE)); + + smartData = Packet::smartResponseMatch(smartList, dgram); + } + + if (sendResponse || !smartData.isEmpty()) { + if(serverResonse(client->get())){ + //TODO: handle if the response faild + } + + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + if ((*client)->dtlsError() == QDtlsError::RemoteClosedConnectionError) knownClients.erase(client); return; @@ -223,6 +256,8 @@ void DtlsServer::decryptDatagram(QDtls *connection, const QByteArray &clientMess Q_ASSERT(connection->isConnectionEncrypted()); const QString peerInfo = peer_info(connection->peerAddress(), connection->peerPort()); + const QString serverInfo = peer_info(serverSocket.localAddress(), serverSocket.localPort()); + const QByteArray dgram = connection->decryptDatagram(&serverSocket, clientMessage); if (dgram.size()) { @@ -233,9 +268,11 @@ void DtlsServer::decryptDatagram(QDtls *connection, const QByteArray &clientMess emit serverPacketReceived(recivedPacket); //if(connection->writeDatagramEncrypted(&serverSocket, tr("to %1: ACK").arg(peerInfo).toLatin1())){ - if(connection->writeDatagramEncrypted(&serverSocket, tr("to %1: %2").arg(peerInfo, QString::fromUtf8(dgram)).toLatin1())){ + if(connection->writeDatagramEncrypted(&serverSocket, tr("from %1: %2").arg(serverInfo, QString::fromUtf8(dgram)).toLatin1())){ std::vector sentPacketInfo = createInfoVect(serverSocket.localAddress(), serverSocket.localPort(), connection->peerAddress(), connection->peerPort()); Packet sentPacket = createPacket(sentPacketInfo, clientMessage, dgram); + QString massageFromTheOtherPeer = "ACK: " + QString::fromUtf8(dgram); + sentPacket.hexString = sentPacket.ASCIITohex(massageFromTheOtherPeer); emit serverPacketSent(sentPacket); } } else if (connection->dtlsError() == QDtlsError::NoError) { @@ -292,6 +329,103 @@ std::vector DtlsServer::createInfoVect(const QHostAddress &fromAddress, } +bool DtlsServer::serverResonse(QDtls* dtlsServer){ + + Packet responsePacket; + responsePacket.init(); + + QSettings settings(SETTINGSFILE, QSettings::IniFormat); + //QString ipMode = settings.value("ipMode", "4").toString(); + QString responseData = (settings.value("responseHex", "")).toString(); + + //dtlsServer->peerAddress(), dtlsServer->peerPort()) + + responsePacket.timestamp = QDateTime::currentDateTime(); + responsePacket.name = responsePacket.timestamp.toString(DATETIMEFORMAT); + responsePacket.tcpOrUdp = "DTLS"; + responsePacket.fromIP = "You (Response)"; + bool isIPv6 = IPv6Enabled(); + if (isIPv6) { + responsePacket.toIP = Packet::removeIPv6Mapping(dtlsServer->peerAddress()); + + } else { + responsePacket.toIP = (dtlsServer->peerAddress()).toString(); + } + responsePacket.port = dtlsServer->peerPort(); + responsePacket.fromPort = serverSocket.localPort(); + responsePacket.hexString = responseData; + QString testMacro = Packet::macroSwap(responsePacket.asciiString()); + responsePacket.hexString = Packet::ASCIITohex(testMacro); + + if (!smartData.isEmpty()) { + responsePacket.hexString = Packet::byteArrayToHex(smartData); + } + + QHostAddress resolved = resolveDNS(responsePacket.toIP); + + if(serverSocket.writeDatagram(responsePacket.getByteArray(), resolved, dtlsServer->peerPort())){ + emit serverPacketSent(responsePacket); + return true; + } + + return false; + +} + + + +bool DtlsServer::IPv6Enabled() +{ + return !IPv4Enabled(); +} + +bool DtlsServer::IPv4Enabled() +{ + QString ipMode = getIPmode(); + if(ipMode == "4") { + return true; + } + return (ipMode.contains("v4") || ipMode.contains(".")); +} + +QString DtlsServer::getIPmode() +{ + QSettings settings(SETTINGSFILE, QSettings::IniFormat); + QString ipMode = settings.value("ipMode", "4").toString(); + + QHostAddress iph = Packet::IPV4_IPV6_ANY(ipMode); + + if(iph == QHostAddress::AnyIPv4) { + return "IPv4 Mode"; + } + if(iph == QHostAddress::AnyIPv6) { + return "IPv6 Mode"; + } + + return ipMode; +} + +QHostAddress DtlsServer::resolveDNS(QString hostname) +{ + + QHostAddress address(hostname); + if (QAbstractSocket::IPv4Protocol == address.protocol()) { + return address; + } + + if (QAbstractSocket::IPv6Protocol == address.protocol()) { + return address; + } + + QHostInfo info = QHostInfo::fromName(hostname); + if (info.error() != QHostInfo::NoError) { + return QHostAddress(); + } else { + + return info.addresses().at(0); + } +} + //void DtlsServer::serverSentDatagram(const QString& peerInfo, const QByteArray &clientMessage, const QByteArray& dgram){ // Packet recPacket; // recPacket.init(); diff --git a/src/dtlsserver.h b/src/dtlsserver.h index 2ae10d32..efcc7fc2 100644 --- a/src/dtlsserver.h +++ b/src/dtlsserver.h @@ -27,9 +27,22 @@ class DtlsServer : public QObject void close(); Packet createPacket(const std::vector& packetInfo, const QByteArray &clientMessage, const QByteArray& dgram); std::vector createInfoVect(const QHostAddress &fromAddress, quint16 fromPort, const QHostAddress &toAddress, quint16 toPort); + bool serverResonse(QDtls* dtlsServer); + + QString getIPmode(); + bool IPv4Enabled(); + bool IPv6Enabled(); + QHostAddress resolveDNS(QString hostname); + + + + + + QUdpSocket serverSocket; + QByteArray smartData; signals: diff --git a/src/packetnetwork.cpp b/src/packetnetwork.cpp index 7a215c3e..d1831eae 100755 --- a/src/packetnetwork.cpp +++ b/src/packetnetwork.cpp @@ -1003,11 +1003,9 @@ void PacketNetwork::packetToSend(Packet sendpacket) QTimer* timer = new QTimer(this); thread->timer = timer; connect(timer, SIGNAL(timeout()), thread, SLOT(onTimeout())); - timer->start(500); + timer->start(10000); } -// if(thread->isFinished()){ -// return; -// } + dtlsthreadList.append(thread); } diff --git a/src/settings.ui b/src/settings.ui index fd4354e1..5f1e2683 100755 --- a/src/settings.ui +++ b/src/settings.ui @@ -355,7 +355,7 @@ - SSL CA Certificates + SSL/DTLS CA Certificates @@ -380,7 +380,7 @@ - SSL Local Certificate + SSL/DTLS Local Certificate @@ -405,7 +405,7 @@ - SSL Private Key + SSL/DTLS Private Key From b65dbbc131c2fb8059df30c17417613b322ff209 Mon Sep 17 00:00:00 2001 From: israel Date: Mon, 11 Dec 2023 22:31:07 +0200 Subject: [PATCH 60/79] all client and server fuctionality is working --- src/dtlsserver.cpp | 33 ++++++++++++++++++++++----------- src/dtlsserver.h | 2 +- src/packetnetwork.cpp | 2 +- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/src/dtlsserver.cpp b/src/dtlsserver.cpp index e9422311..5c38cd6a 100644 --- a/src/dtlsserver.cpp +++ b/src/dtlsserver.cpp @@ -142,14 +142,25 @@ void DtlsServer::readyRead() //! [6] if ((*client)->isConnectionEncrypted()) { + QSettings settings(SETTINGSFILE, QSettings::IniFormat); + //TODO: split into two function, one for decryption and one for writting - decryptDatagram(client->get(), dgram); + QDtls * dtlsServer = client->get(); + dgram = dtlsServer->decryptDatagram(&serverSocket, dgram); + +// bool sendSimpleAck = settings.value("sendSimpleAck", false).toBool(); +// if(sendSimpleAck){ +// sendAck(dtlsServer, dgram); +// } + + sendAck(dtlsServer, dgram); + ///////////////////////////////////////////////////manage send response option////////////////////////////////////////////////////////////// - QSettings settings(SETTINGSFILE, QSettings::IniFormat); + //QSettings settings(SETTINGSFILE, QSettings::IniFormat); bool sendResponse = settings.value("sendReponse", false).toBool(); bool sendSmartResponse = settings.value("sendReponse", false).toBool(); @@ -251,27 +262,27 @@ void DtlsServer::doHandshake(QDtls *newConnection, const QByteArray &clientHello //! [11] //! [12] -void DtlsServer::decryptDatagram(QDtls *connection, const QByteArray &clientMessage) +void DtlsServer::sendAck(QDtls *connection, const QByteArray &clientMessage) { Q_ASSERT(connection->isConnectionEncrypted()); const QString peerInfo = peer_info(connection->peerAddress(), connection->peerPort()); const QString serverInfo = peer_info(serverSocket.localAddress(), serverSocket.localPort()); - const QByteArray dgram = connection->decryptDatagram(&serverSocket, clientMessage); + //const QByteArray dgram = connection->decryptDatagram(&serverSocket, clientMessage); - if (dgram.size()) { + if (clientMessage.size()) { //the vector content: createInfoVect(const QHostAddress &fromAddress, quint16 fromPort, const QHostAddress &toAddress, quint16 toPort) //TODO: insert the vector error massage std::vector recievedPacketInfo = createInfoVect(connection->peerAddress(), connection->peerPort(), serverSocket.localAddress(), serverSocket.localPort()); - Packet recivedPacket = createPacket(recievedPacketInfo, clientMessage, dgram); + Packet recivedPacket = createPacket(recievedPacketInfo, clientMessage, clientMessage); emit serverPacketReceived(recivedPacket); //if(connection->writeDatagramEncrypted(&serverSocket, tr("to %1: ACK").arg(peerInfo).toLatin1())){ - if(connection->writeDatagramEncrypted(&serverSocket, tr("from %1: %2").arg(serverInfo, QString::fromUtf8(dgram)).toLatin1())){ + if(connection->writeDatagramEncrypted(&serverSocket, tr("from %1: %2").arg(serverInfo, QString::fromUtf8(clientMessage)).toLatin1())){ std::vector sentPacketInfo = createInfoVect(serverSocket.localAddress(), serverSocket.localPort(), connection->peerAddress(), connection->peerPort()); - Packet sentPacket = createPacket(sentPacketInfo, clientMessage, dgram); - QString massageFromTheOtherPeer = "ACK: " + QString::fromUtf8(dgram); + Packet sentPacket = createPacket(sentPacketInfo, clientMessage, clientMessage); + QString massageFromTheOtherPeer = "ACK: " + QString::fromUtf8(clientMessage); sentPacket.hexString = sentPacket.ASCIITohex(massageFromTheOtherPeer); emit serverPacketSent(sentPacket); } @@ -362,8 +373,8 @@ bool DtlsServer::serverResonse(QDtls* dtlsServer){ } QHostAddress resolved = resolveDNS(responsePacket.toIP); - - if(serverSocket.writeDatagram(responsePacket.getByteArray(), resolved, dtlsServer->peerPort())){ + serverSocket.waitForBytesWritten(); + if(dtlsServer->writeDatagramEncrypted(&serverSocket,responsePacket.getByteArray())){ emit serverPacketSent(responsePacket); return true; } diff --git a/src/dtlsserver.h b/src/dtlsserver.h index efcc7fc2..4f35fe54 100644 --- a/src/dtlsserver.h +++ b/src/dtlsserver.h @@ -68,7 +68,7 @@ public slots: const QByteArray &clientHello); void doHandshake(QDtls *newConnection, const QByteArray &clientHello); - void decryptDatagram(QDtls *connection, const QByteArray &clientMessage); + void sendAck(QDtls *connection, const QByteArray &clientMessage); void shutdown(); bool listening = false; diff --git a/src/packetnetwork.cpp b/src/packetnetwork.cpp index d1831eae..e17b7e9f 100755 --- a/src/packetnetwork.cpp +++ b/src/packetnetwork.cpp @@ -1003,7 +1003,7 @@ void PacketNetwork::packetToSend(Packet sendpacket) QTimer* timer = new QTimer(this); thread->timer = timer; connect(timer, SIGNAL(timeout()), thread, SLOT(onTimeout())); - timer->start(10000); + timer->start(500); } dtlsthreadList.append(thread); From bf783138598f3d02eeea0f344c52e4e1812a6111 Mon Sep 17 00:00:00 2001 From: israel Date: Tue, 12 Dec 2023 10:42:44 +0200 Subject: [PATCH 61/79] sendSimpleAck checkBox is working, but the server can't present the packet he recieved --- src/dtlsserver.cpp | 4 +++- src/mainwindow.cpp | 13 +++++++++++++ src/mainwindow.h | 1 + src/settings.cpp | 15 ++++++++++++++- src/settings.h | 1 + src/settings.ui | 7 +++++++ 6 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/dtlsserver.cpp b/src/dtlsserver.cpp index 5c38cd6a..6a240bf0 100644 --- a/src/dtlsserver.cpp +++ b/src/dtlsserver.cpp @@ -153,7 +153,9 @@ void DtlsServer::readyRead() // sendAck(dtlsServer, dgram); // } - sendAck(dtlsServer, dgram); + if(settings.value("sendSimpleAck").toString() == "true"){ + sendAck(dtlsServer, dgram); + } ///////////////////////////////////////////////////manage send response option////////////////////////////////////////////////////////////// diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 6e0c73f1..8f73926f 100755 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2322,6 +2322,19 @@ void MainWindow::on_leaveSessionOpen_StateChanged(){ } } +void MainWindow::on_sendSimpleAck_StateChanged(){ + //ui.checkBox->setChecked(checkBoxState); + + QSettings settings(SETTINGSFILE, QSettings::IniFormat); + QString sendSimpleAck = settings.value("sendSimpleAck", "false").toString(); + if(sendSimpleAck == "false"){ + settings.setValue("sendSimpleAck", "true"); + } + else{ + settings.setValue("sendSimpleAck", "false"); + } +} + void MainWindow::on_actionSettings_triggered() { Settings settings(this); diff --git a/src/mainwindow.h b/src/mainwindow.h index 13824c38..48094680 100755 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -97,6 +97,7 @@ class MainWindow : public QMainWindow void sendPacket(Packet sendpacket); public slots: + void on_sendSimpleAck_StateChanged(); void on_leaveSessionOpen_StateChanged(); void toTrafficLog(Packet logPacket); void cancelResends(); diff --git a/src/settings.cpp b/src/settings.cpp index 3caba864..036b41ca 100755 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -99,8 +99,21 @@ Settings::Settings(QWidget *parent, MainWindow* mw) : //not working yet... ui->multiSendDelayLabel->hide(); ui->multiSendDelayEdit->hide(); - //connect(loadKeyButton, &QPushButton::clicked, this, &MainWindow::on_loadKeyButton_clicked); + //connect(sendSimpleAck, &QCheckBox::clicked, this, &MainWindow::on_loadKeyButton_clicked); + + if(settings.value("sendSimpleAck").toString() == "false"){ + ui->sendSimpleAck->setChecked(false); + } else { + ui->sendSimpleAck->setChecked(true); + } + + sendSimpleAck = ui->sendSimpleAck; + //MainWindow *mainWindow = qobject_cast(parent); + connect(sendSimpleAck, &QCheckBox::toggled, dynamic_cast(parent), &MainWindow::on_sendSimpleAck_StateChanged); + +// PacketNetwork *parentNetwork = qobject_cast(parent()); +// connect(this, SIGNAL(packetReceived(Packet)), parentNetwork, SLOT(toTrafficLog(Packet))); QIcon mIcon(":pslogo.png"); diff --git a/src/settings.h b/src/settings.h index 37cfd63b..48af666a 100644 --- a/src/settings.h +++ b/src/settings.h @@ -67,6 +67,7 @@ class Settings : public QDialog public: MainWindow* rmw; + QCheckBox* sendSimpleAck; explicit Settings(QWidget *parent = nullptr, MainWindow* mw = nullptr); ~Settings(); diff --git a/src/settings.ui b/src/settings.ui index 5f1e2683..0d498fa7 100755 --- a/src/settings.ui +++ b/src/settings.ui @@ -167,6 +167,13 @@ + + + + Send simple Acknowledge (for DTLS server only) + + + From 5a73cd4fb6b2150004a08488d048ab4d294c2178 Mon Sep 17 00:00:00 2001 From: israel Date: Tue, 12 Dec 2023 12:16:32 +0200 Subject: [PATCH 62/79] all client-server functionality is working, including: simple ACK, simple response and smart response --- src/dtlsserver.cpp | 16 +++++++++------- src/dtlsserver.h | 2 +- src/settings.ui | 10 +++++----- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/dtlsserver.cpp b/src/dtlsserver.cpp index 6a240bf0..3ab0b829 100644 --- a/src/dtlsserver.cpp +++ b/src/dtlsserver.cpp @@ -153,6 +153,12 @@ void DtlsServer::readyRead() // sendAck(dtlsServer, dgram); // } + //the vector content: createInfoVect(const QHostAddress &fromAddress, quint16 fromPort, const QHostAddress &toAddress, quint16 toPort) + //TODO: insert the vector error massage + std::vector recievedPacketInfo = createInfoVect(dtlsServer->peerAddress(), dtlsServer->peerPort(), serverSocket.localAddress(), serverSocket.localPort()); + Packet recivedPacket = createPacket(recievedPacketInfo, dgram); + emit serverPacketReceived(recivedPacket); + if(settings.value("sendSimpleAck").toString() == "true"){ sendAck(dtlsServer, dgram); } @@ -274,16 +280,12 @@ void DtlsServer::sendAck(QDtls *connection, const QByteArray &clientMessage) //const QByteArray dgram = connection->decryptDatagram(&serverSocket, clientMessage); if (clientMessage.size()) { - //the vector content: createInfoVect(const QHostAddress &fromAddress, quint16 fromPort, const QHostAddress &toAddress, quint16 toPort) - //TODO: insert the vector error massage - std::vector recievedPacketInfo = createInfoVect(connection->peerAddress(), connection->peerPort(), serverSocket.localAddress(), serverSocket.localPort()); - Packet recivedPacket = createPacket(recievedPacketInfo, clientMessage, clientMessage); - emit serverPacketReceived(recivedPacket); + //if(connection->writeDatagramEncrypted(&serverSocket, tr("to %1: ACK").arg(peerInfo).toLatin1())){ if(connection->writeDatagramEncrypted(&serverSocket, tr("from %1: %2").arg(serverInfo, QString::fromUtf8(clientMessage)).toLatin1())){ std::vector sentPacketInfo = createInfoVect(serverSocket.localAddress(), serverSocket.localPort(), connection->peerAddress(), connection->peerPort()); - Packet sentPacket = createPacket(sentPacketInfo, clientMessage, clientMessage); + Packet sentPacket = createPacket(sentPacketInfo, clientMessage); QString massageFromTheOtherPeer = "ACK: " + QString::fromUtf8(clientMessage); sentPacket.hexString = sentPacket.ASCIITohex(massageFromTheOtherPeer); emit serverPacketSent(sentPacket); @@ -307,7 +309,7 @@ void DtlsServer::shutdown() //! [14] //this function using for creation packet that can be sent to packetReceivedECHO -Packet DtlsServer::createPacket(const std::vector& packetInfo, const QByteArray &clientMessage, const QByteArray& dgram){ +Packet DtlsServer::createPacket(const std::vector& packetInfo, const QByteArray& dgram){ Packet recPacket; recPacket.init(); diff --git a/src/dtlsserver.h b/src/dtlsserver.h index 4f35fe54..16db6cbd 100644 --- a/src/dtlsserver.h +++ b/src/dtlsserver.h @@ -25,7 +25,7 @@ class DtlsServer : public QObject bool listen(const QHostAddress &address, quint16 port); bool isListening() const; void close(); - Packet createPacket(const std::vector& packetInfo, const QByteArray &clientMessage, const QByteArray& dgram); + Packet createPacket(const std::vector& packetInfo, const QByteArray& dgram); std::vector createInfoVect(const QHostAddress &fromAddress, quint16 fromPort, const QHostAddress &toAddress, quint16 toPort); bool serverResonse(QDtls* dtlsServer); diff --git a/src/settings.ui b/src/settings.ui index 0d498fa7..32f431a2 100755 --- a/src/settings.ui +++ b/src/settings.ui @@ -7,7 +7,7 @@ 0 0 1013 - 583 + 601 @@ -161,16 +161,16 @@ - + - Send a basic response with macro support + Send simple Acknowledge (for DTLS server only) - + - Send simple Acknowledge (for DTLS server only) + Send a basic response with macro support From 8966c09f1e6f9749d222533cafd42bc598f4999e Mon Sep 17 00:00:00 2001 From: israel Date: Tue, 12 Dec 2023 14:35:48 +0200 Subject: [PATCH 63/79] the client and the server can present error if: handshake failed or writing massage failed --- src/dtlsserver.cpp | 15 +++++++++++---- src/dtlsthread.cpp | 19 +++++++++++++++++++ src/dtlsthread.h | 1 + 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/dtlsserver.cpp b/src/dtlsserver.cpp index 3ab0b829..2013482d 100644 --- a/src/dtlsserver.cpp +++ b/src/dtlsserver.cpp @@ -283,12 +283,15 @@ void DtlsServer::sendAck(QDtls *connection, const QByteArray &clientMessage) //if(connection->writeDatagramEncrypted(&serverSocket, tr("to %1: ACK").arg(peerInfo).toLatin1())){ + std::vector sentPacketInfo = createInfoVect(serverSocket.localAddress(), serverSocket.localPort(), connection->peerAddress(), connection->peerPort()); + Packet sentPacket = createPacket(sentPacketInfo, clientMessage); if(connection->writeDatagramEncrypted(&serverSocket, tr("from %1: %2").arg(serverInfo, QString::fromUtf8(clientMessage)).toLatin1())){ - std::vector sentPacketInfo = createInfoVect(serverSocket.localAddress(), serverSocket.localPort(), connection->peerAddress(), connection->peerPort()); - Packet sentPacket = createPacket(sentPacketInfo, clientMessage); QString massageFromTheOtherPeer = "ACK: " + QString::fromUtf8(clientMessage); sentPacket.hexString = sentPacket.ASCIITohex(massageFromTheOtherPeer); emit serverPacketSent(sentPacket); + }else{ + sentPacket.errorString = "Could not send response"; + emit serverPacketSent(sentPacket); } } else if (connection->dtlsError() == QDtlsError::NoError) { emit warningMessage(peerInfo + ": " + tr("0 byte dgram, could be a re-connect attempt?")); @@ -376,14 +379,18 @@ bool DtlsServer::serverResonse(QDtls* dtlsServer){ responsePacket.hexString = Packet::byteArrayToHex(smartData); } - QHostAddress resolved = resolveDNS(responsePacket.toIP); + //QHostAddress resolved = resolveDNS(responsePacket.toIP); serverSocket.waitForBytesWritten(); if(dtlsServer->writeDatagramEncrypted(&serverSocket,responsePacket.getByteArray())){ emit serverPacketSent(responsePacket); return true; + }else{ + responsePacket.errorString = "Could not send response"; + emit serverPacketSent(responsePacket); + return false; + } - return false; } diff --git a/src/dtlsthread.cpp b/src/dtlsthread.cpp index 57dc2890..69bb34a1 100644 --- a/src/dtlsthread.cpp +++ b/src/dtlsthread.cpp @@ -78,7 +78,11 @@ void Dtlsthread::handShakeComplited(){ void Dtlsthread::writeMassage(Packet packetToSend, DtlsAssociation* dtlsAssociation){ const qint64 written = dtlsAssociation->crypto.writeDatagramEncrypted(&(dtlsAssociation->socket), packetToSend.asciiString().toLatin1()); if (written <= 0) { + packetToSend.errorString = "Failed to send"; //emit errorMessage(tr("%1: failed to send a ping - %2").arg(name, crypto.dtlsErrorString())); + if(dtlsAssociation->crypto.isConnectionEncrypted()){ + emit packetSent(packetToSend); + } return; } emit packetSent(packetToSend); @@ -88,6 +92,7 @@ void Dtlsthread::writeMassage(Packet packetToSend, DtlsAssociation* dtlsAssociat void Dtlsthread::persistentConnectionLoop() { + QUdpSocket* clientConnection = &(dtlsAssociation->socket); QDEBUG() << "Entering the forever loop"; int ipMode = 4; @@ -318,6 +323,18 @@ void Dtlsthread::onTimeout(){ dtlsAssociation->closeRequest = true; //closeRequest = true; timer->stop(); + if(!(dtlsAssociation->crypto.isConnectionEncrypted())){ + sendpacket.errorString = "Could not connect"; + emit packetSent(sendpacket); + } + + +// if(!(dtlsAssociation->crypto.isConnectionEncrypted())){ +// createErrorPacket(){ + +// } +// } + //dtlsAssociation->crypto.abortHandshake(&(dtlsAssociation->socket)); //dtlsAssociation->socket.close(); //dtlsAssociation->deleteLater(); @@ -347,6 +364,8 @@ DtlsAssociation* Dtlsthread::initDtlsAssociation(){ return dtlsAssociationP; } + + /////////////////////////backups/////////////////// //void Dtlsthread::addServerResponse(const QString &clientAddress, const QByteArray &datagram, const QByteArray &plainText, QHostAddress serverAddress, quint16 serverPort, quint16 userPort) //{ diff --git a/src/dtlsthread.h b/src/dtlsthread.h index 202b8046..da01c7ef 100644 --- a/src/dtlsthread.h +++ b/src/dtlsthread.h @@ -38,6 +38,7 @@ public slots: void receivedDatagram(QByteArray plainText); signals: + void toStatusBar(const QString & message, int timeout = 0, bool override = false); void connectStatus(QString); void packetSent(Packet); void packetReceived(Packet); From fbdfff00f6485139b46cdddd3e61cc527568567f Mon Sep 17 00:00:00 2001 From: israel Date: Sun, 17 Dec 2023 12:05:10 +0200 Subject: [PATCH 64/79] the send and recieve symbols at the trafficLog are represent correctly (with the correct color) --- src/dtlsserver.cpp | 18 ++++++++++++------ src/dtlsthread.cpp | 2 +- src/icons/tx_dtls.png | Bin 0 -> 45053 bytes src/packet.cpp | 11 +++++++++++ 4 files changed, 24 insertions(+), 7 deletions(-) create mode 100644 src/icons/tx_dtls.png diff --git a/src/dtlsserver.cpp b/src/dtlsserver.cpp index 2013482d..fc5c6896 100644 --- a/src/dtlsserver.cpp +++ b/src/dtlsserver.cpp @@ -322,14 +322,20 @@ Packet DtlsServer::createPacket(const std::vector& packetInfo, const QB recPacket.port = packetInfo[3].toUInt(); QString massageFromTheOtherPeer = QString::fromUtf8(dgram); recPacket.hexString = recPacket.ASCIITohex(massageFromTheOtherPeer); - recPacket.errorString = "none"; + //recPacket.errorString = "none"; recPacket.tcpOrUdp = "DTLS"; - if((packetInfo[0] == "0.0.0.0") || (packetInfo[0] == "127.0.0.1")){ - recPacket.fromIP = "You"; +// if((packetInfo[0] == "0.0.0.0") || (packetInfo[0] == "127.0.0.1")){ +// recPacket.fromIP = "You"; +// } +// if((packetInfo[2] == "0.0.0.0") || (packetInfo[2] == "127.0.0.1")){ +// recPacket.toIP = "You"; +// } + if(packetInfo[0] == "0.0.0.0"){ + recPacket.fromIP = "you"; } - if((packetInfo[2] == "0.0.0.0") || (packetInfo[2] == "127.0.0.1")){ - recPacket.toIP = "You"; + if(packetInfo[2] == "0.0.0.0"){ + recPacket.toIP = "127.0.0.1"; } return recPacket; @@ -361,7 +367,7 @@ bool DtlsServer::serverResonse(QDtls* dtlsServer){ responsePacket.timestamp = QDateTime::currentDateTime(); responsePacket.name = responsePacket.timestamp.toString(DATETIMEFORMAT); responsePacket.tcpOrUdp = "DTLS"; - responsePacket.fromIP = "You (Response)"; + responsePacket.fromIP = "You"; bool isIPv6 = IPv6Enabled(); if (isIPv6) { responsePacket.toIP = Packet::removeIPv6Mapping(dtlsServer->peerAddress()); diff --git a/src/dtlsthread.cpp b/src/dtlsthread.cpp index 69bb34a1..2c5b57d5 100644 --- a/src/dtlsthread.cpp +++ b/src/dtlsthread.cpp @@ -283,7 +283,7 @@ void Dtlsthread::receivedDatagram(QByteArray plainText){ //recPacket.hexString = massageFromTheOtherPeer; recPacket.toIP = dtlsAssociation->socket.localAddress().toString(); recPacket.port = dtlsAssociation->socket.localPort(); - recPacket.errorString = "none"; + //recPacket.errorString = "none"; recPacket.tcpOrUdp = "DTLS"; emit packetReceived(recPacket); diff --git a/src/icons/tx_dtls.png b/src/icons/tx_dtls.png new file mode 100644 index 0000000000000000000000000000000000000000..e895137ecd9d1b8e9525591e9e301b68e93c4086 GIT binary patch literal 45053 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 Date: Sun, 17 Dec 2023 13:37:24 +0200 Subject: [PATCH 65/79] all the dtls icons appears correctly --- src/globals.h | 1 + src/icons/rx_dtls.png | Bin 0 -> 69289 bytes src/icons/tx_dtls.png | Bin 45053 -> 38892 bytes src/mainwindow.cpp | 2 +- src/mainwindow.ui | 4 ++-- src/packet.cpp | 4 ++-- src/packetsender.qrc | 2 ++ 7 files changed, 8 insertions(+), 5 deletions(-) create mode 100644 src/icons/rx_dtls.png diff --git a/src/globals.h b/src/globals.h index 483ce9e4..62e9e780 100755 --- a/src/globals.h +++ b/src/globals.h @@ -50,6 +50,7 @@ #define NAMEINIKEY "NAMES" #define DTLSSENDICON ":icons/tx_dtls.png" +#define DTLSRXICON ":icons/rx_dtls.png" #define UDPSENDICON ":icons/tx_udp.png" #define TCPSENDICON ":icons/tx_tcp.png" #define UDPRXICON ":icons/rx_udp.png" diff --git a/src/icons/rx_dtls.png b/src/icons/rx_dtls.png new file mode 100644 index 0000000000000000000000000000000000000000..dd279d6bc148c0141e388604bcbb39487e6008dc GIT binary patch literal 69289 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%0Ap-Yk@R$DQeILLughiXn{} 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 literal 0 HcmV?d00001 diff --git a/src/icons/tx_dtls.png b/src/icons/tx_dtls.png index e895137ecd9d1b8e9525591e9e301b68e93c4086..d9843f34559d5eed9d7028dbd7d18545a1235480 100644 GIT binary patch literal 38892 zcmc$Fg;$hcv^E`5Gjzv*gn*>v&_g#UDIh5+(lvm<2t&8uGQ8MzAvHF=Jfdu~>ibbe#wO>ad-Ma0R;36lz~ z>VKyfXJ_-lrz5Hl^K*;$=h!n((RY$Q-bu{zaM^9;3OmZh#wSJg6Rd14EG!%i4i2)h z$3K@|5EBs@6B87CKQx#l{gBa1vv@V=m*9}00&#r?8HG=uUjEyZ^*bKZmAgyWM1P})CMoZct*tGAOr$>)3QgvKO3BFNOg9`XFE1ODkdQRZs%`u+r9cb*1TDB2 z|GQtyk#0jW7ypV>sEujakWf!gVgAE|105-8>9HI72ErN`CoNGx5L7OtsnBS%U&ccXwS} z6#U752!!qs!+(r#oe)ez4AI258}9cToo7>4aw$d$@aZM^AzC1&cgUkIk@LgF2P5{F z(&FM`KL%6R^{OY-;WvWj|w(4G!%dV{P*mxL~-{xS&0qyg*npb^!a4l zt5@~e9qJr=lMp=p^$ zSOS1mZ2)z48>I-QE}toji_d5PxVVYGCut;V_G7C?HR1!9i2{6B>1*4*JZF!M-D9ql zgj!P3mBVbb^{ji4Cw{8=37}isf9~C^ z_EPmnkk8B!PhIScHHWa{#rAFa;&pkIZpd+}l4&{|?vu!#@e&ARz^H4Swl2MQK_m3E zvF@jY?&8kI2xS1vsf_8?-A6cfNd*^uI_AW>;j7@_;B6Yr@&l2(ySv5t=HQ#nde@ob zd8_+%tNQ>m9*|8AJ6bJ~9o@3$RhV=7&?U6RKD>mfsYjG*zfsXqlK~ z`Z?}k6ph5bwDMt=jZcbNQ^RP|F+<}C;9iVgWD8k8zElOd(=jtQkFJs6lQR0xkcj^q z&Ow9Nq#Irctjd2s?=#iZ8-C`V7$09GC#$A6W&5;9o+E!eLMni~-KsxeHsd|9KApAO z&wJ#|wO3Jk%ijvg>U{LGf}s`8fcI!*71c|(LvHC0&x~%s2`hJ4i1!Ap7Y48&mJPqd zcal5>WWI(=lnHd3w1mv`tnM(lBae=of-ZcE(%C_iY%9gZph+2%CWJ>9-QmxiKP;KZ z&#FoU(qSmu+B9{XoQpF>m=6*LTg#WtM15=#iSM~qRZ($xIFh^XP7FT%!V({v$Zj*A ze@y-iRPfQGgGAih1aLoqhES-aoZJIeuwtLwXL7LhRt`jlbyV-!mgTL^tI*Jv`yXS! zimc{-`|Nh>N^KWb9?bkKJ`LIf0(Wtaj$U^Djs$j9vPgMvk?{wISq7e%JOQ*S13-l8 z3l$h8NHxWhzl+@zs(ioiYt6b&d2Dqt zxI=5al_8K__5)GS*<%-^Q%Pe`BM_c1nSXD9h%Az8N2r zmVo*rYR$#tj{?x;@ES!(r<6R}x%fm4K{rG)On=M(T7xz(SluqTN8+yMgl|3+3TD<^_2t19z=Ewh>BkpWhb>%TRUbS!GL-mJYRVR&k*6b4NM6!GTQm3OHIfmLnJLlRbt{%C4m~Ti zl94TC29A)U4R<8va!Xb2nwmZCVW4NFsH{D#2ShxW0$!VNDWq&ea8U&$y%o2Ic{C?? z2Sr6jR@{$fG9E(={NjS{QM6l4 zai{IZa{S|W;(PPv4QqOHbMwvb#>STM#l?qpmx>0?knhh5wjaeX5Ny9}MWIkE0|Ns& zIvDm71MCcKsY+_p8s8e#j6~_9Rqib5n&Pd4Z z{x|riHuO$LT+mj1@965fr^T$U@Wd=2mJtcl!+7VTZxz;+FIv{E={e`)_98gj*`5g< z;&fHfUZm^tqKo}=*W4otJ!Mu9G44z}1`vm* zk!)XH4YQGdVUw7YRJJPk=A0ox_Ihi#qWOAUHDyphhUSrzgO{|Y<(L?hi$7gYZ&wt8a z;;T?suFm5o+7hG$s%PWtf}WJ^YCj8;8jT2v6AEhaIzZdfL>)uWym9k!`p-veBT<5a zsYXQ6c8a)CCJKKT4qsxp#MS_RT1ho{@(HW-eGlwqV#%?dw%|Llm}^qndYT9Sgd_G_ zor$F-Rz%sgk)Pj%c2iSRJ;Jk(C&=$`K_>MD1+ePtqmthL^X%u((UmzHcb_9&WuYg` z?k81ZDPPC+|3!ac$2J2W{w3p~MmCDsn9$rT8}idR|L0GyN??EaT)q2)Iq%JV*23x6 zE;)L%OT%W5P9=;f%VQ&AZb>3r?zQ66<>wu-L>;_AnAydQ;p^DgTZ5!TjC{Uk-arglPqThR;fI0PY}9HWaZxo z{BvmfLpoknKTwzLvRg>L%@Eb*^Cc#)Dpsoqe_PESaBz8f8SvFuRu3i2p6XQ&zV_+- zY<;yL4zeM6qfoZ5X>~m+kPsJlTU8FXa!6@zS-Juznqss8$&X51a5dWSC*tFf9_T>O z&N@Nl>0M|s+uGW?Rp&O>>~u`kf8xb17ciE!Ao|Lg4O9?(5j(<3bWH2m>>a69>TV=1f>PHscYxDjaFtBx=g0Rnv#&~EHS$AI`Ew5u%w|KZ#Ivjtnod&)+l+z$39Fc4;P zBn~C+$N--AS~R<^JAja-dw+kQsha6uFy{qr0Cdim*>8X5=Vmlooe-u+)}{R(4PgsG zFc=-fjXn1N{?($v$y}N`$rNnMPtSwI;N|Jm0TrZAZq|Q|! zkNdbZYuQ0YQRq>L4e|GZ7nYxvfln&_`7+fOw2z*=RLZ0|hTGLlC=JJ7^;A+!u z{xaz7R5k0%yi(Qa}aoYDT!6FLSC6m;Zz$AU|dvm^jLFb`Wm<|FN!7oRU@ZZam=I%06 zzBJIn^Vx6TzL7R-VzAjIYEc`vhomzS+S&uIo@`MnbOS_Ff)T5Gy}i9e-QC>+kBsW% ze9rx_|9Ldr1h}HZUhw?a`X}?};?+yV3Jdma-?DVoj?{V7K(}d>jl}z+HFE%PK7>zI z?Z@(Sb}0DIJM8&Kj?a_Y!bD4Z`7Si{0Xrfhf~-sR6_8i?bQu0>m0|I+YYrVyL#M*W zU)?59A`*m;)mmcnneZ?Z^0*2BPMtYjJ4@6({p2E zUb;|8<|=Zuw=tn=zCWvM337DY^6T#EvMiVX^Mt0I^%dZxaJiSgz&B6sl(5iTmo}W> zzr*)-h%)-xlSH$=pal1Ki_Le^l>q}T%<8<#keAWuC(ql3hSV)Oo`gCy#_>*@5+q^5 zmemz7<3uURSWZC+u&E4hAgRpV_hnnQBa`QUZtDOpq#^#W#^$fXmG^;GZ zI#TxUj6wN@UieiC7F<%L@?OFy3+8dd)G^~&i!DBK5JN3NeP7g!0|N2JR~fwu5P6R= zZee3TUF>a;*7N4GNprtKG&B7E$Xq^qpczTiIM$G~cE!dWE+Pf1%T(o zL75I%(Zb6zvEno&TKClqGRN@*@2I73l<4AMzLCF)6;yz=GWxv08RemEUe!E`H6Sqi z-zcbU27libT zRrV-063fdBXu7+*YcQho12R89ztFh^_c?T$P%aR9)z6~Es|s0T4SWYtrnSy?T0>E% zE((uIWPHHZM?o3c06g572{NW-pG4bT#EFc)uvbjj@X5 zKv_YkEX!}-LB0e54yM|U{2c(=qFwob@&Ti{)5#-u8uVqfS_@)o^gFb?HhB$!J#L=u z0C}Qw&#^m4$_%~?>gebYSTbLLO7+RbV9lzl$!AW+{3uVa`)Qfa%Dmwc)?)b$c4f%C zbw?qcSZ-cVn)T07Ize}CaJ7y|0Qut-+Xe`XoaGm?F?@5l&{RA<815eU(7Z3Kv-(k7 zK<}y`)2JB7lR#;xXP4^VM8d3b*0Z@1J&cRCfB1gs<#;s;S$|my&VMd`)7VV8U{s-v z-WQX#l}=2Yd0hYbuO+=nNGGQ%8b$2yyP5ou{x&w@i%HjRsjG1kqX^s)@mMNXf~`Qv z$2HuubR4N!fN4^o;*+_H5>-7noS3e>`=A0nOQ&*z!=k#&8ioQ@!*N@hUasX2KwN23 zX5)FMV};bp=F=j9csXfuAli>gUXn%v|Hz4?_-B-Zg9t!S53;SqTYxu4+iRFUgg$Tb zjVc2u7CcIDSe5c-_DK&zRA=hP!by+Fee7C7Q~yzU6AXDqWW7!9j0cL4H<+-a|ZVXTM+{>ob7^rEkkI6Q^` zC+y`*viA%4Y`8_~@Y$r2;G%%)1-*B~)(zg|JKxL*=M*xWtRF27mC@WY=!4Bp4r)oZ2mH@Eq)%IQ3pDE^6Y$YD3_El}n z`OmWplbPk#3?&3rpTrXQ6v@7KKZbk94Q|ua4Npz^O{=X&Jk||#(a9eXLYEifDuoUb zYDXO`_o$cbaf`jOFMNLECWsLkiBN+%`J|n~BIa9RMJLqWU(4b!#B8>cilX>s9EELs zRLV7eyDXVWcfVMrK6QNC7e1VpuI=mRr#qeC@b^IS9Gww)T}}0gJeKif@X46LSuAQb zvUicm-E?U#RIdAcc;@ToOs5Q&7$jTLrGZ^tU)W@8?Y1p7Ti#&ags{hP?frwV8*Yfo zd^NP*uQ8pq-aj=v$}8frHnH%1n}mlm<}_-(KJHSVCY->Qo5sZVar{5`5S>mP{?pkr zRa1mct#|26dr0Gj1v<1^A(z65m^VJd2Oh&%>@>PErb9IN)+EnApUKyJDIhn!0yoVg z)sz>({@DazT`FWd<3!|YXaufBX8^rpI?^P<-(1%Iw-Ob))QTAren(d3-pFH1_3pw{ zg;rwJYO)~6j0A1MX3Q$|P1Jfc4;i=TS(FbKO)*PWn>pjH_ zU5xNRjW}4|S1z_GyPEv(5?WeXDDAh+^Vy(84sP>Aa>aEq%jWl^Gb1BtM_RD{C`plZ z>HfRe{8(v{qLlb23|?74htnbWCneE7_@}PZQ&G8qs=b`Z?m!N};#&dCN1pcd@!lfq zu%B~SI|VtPh-#J_jM70{*+sXPhN?k9(l9!7y85V-(M$wl)7Wfm=|7tHytpeyAKBre zR1%Y!{6kR~AduN(vDu6sIrL*m9b-V_SbrPJn_p7-te$d|_OYa>Nre6n!Q+3Zk;DgE zTeKe}v_`8v*IFCSSg~wV{HKNFT$<-c+se2>1@=EeKE3tnj3ASpw%tPH?W zBkoijkGK%K)h4^5aFUAloe!Vb{?TBW{c?=#RRe5S%5`u|jQi(rrv{6zMa<w>VZX_&Zyq$A;R|sPld$a;d7I-y%`{VEg&enYQ_D=Q zk-OgdV$Lr!$$4+WD>cG-V5m9K0rZhx#{W~tsok1&7Usq#H!UbV4ll0BAN7!Fjq`gK|ZTN6WX zZHX_0MJnc1sNb57sB`WykG^gH?}wD7C}|Pfk8TvBy;?2G$2f34@WUy`U8dIO4#pa) zDZ>rE#Lu@q$2VoSm|l5z6ciMO)C8M+f`hs`I)kqQ)6QG5$y_De>;Uk@72()E7OlBT-IZ|Jzk_q^(WhdrrnXEwK)xP zUv*m2nciN2XK(ksT@I8#7=aCW18vA2dl zFy*h`QsO18b7CYXVFOWk>rwo8LIY_&OhzX!KyXyO;wR! z=0D$qhRyrG3H^R$AFCvOpJ7?r=xS>>wXJ3FPs`I5&*4<3^f~isGr#e?TPw|ex&Dk- zk$*GxIJ^&vz*rABGp9PWx_cEvVVdg)cAY^~rA>(bXdILBse^&Qvjbaqtd?U+$Zcev;-7iV>jK|4N*rm{;9{5LB ziL-X(rm@FQ#Ko=~FZ4oV>{)0J`U4L?f^bt%YhZL81~G8P1j50&D4E1;<91 zcS?!jQ%*Wh6u|C0LjmehW%ngTd&2&`J({;p6wLYx!fEj{U|BMc>4btYYHU^0z|Z@_ zgH;V7>FYx^r*K{MnKRRO0nO?8_ctC%-S7B+6bf|2bBxa8tyy1rP{{JA=A;p55!}Yc zK3z0w3bTy8-Wwv6dj5OxOUY|)KiR|ACL4*R-^iX>V80xs1(M~kpPgk|QR8yz&VfV6 zHcs)(DWP|JKZm;Rr${EFGU!4sJ6LjE6jreTk;#7uIk#0QsUTn{L7~Eco~$*L1Y{w= z%1GO?hZno{lQ^~~MqY@%(bq(<2AvSl>4$}`+`N7FHdo^w?aEToUE09%kD{nJ!3|gA zwv0KQnyZlfBATE5!%&f_-<1dZC*`Eq2e`w2uGBX2tnqPz24QN#qb4=`NZ-Ev&r$(B zW2IBld2Ywe0{6cQf{b?z{TEm^EgEO9^G09DuZP5i+`{)>4*#nX?4icLn9a6y@AR0L z+{`|)Gb&&AD11@FdcHq*Aw|#;-eu0m#Nlu9M*UAJE=97?Re>$0oQGB!!V$n6RkDus zb6mvFFDWYbJvhVuCg`9Rny|eAwNWNhFB39qQDWXCSt$3hK9pmEUBv_S@3GVkplvVf%Wzbnmn6MqaPR6?@sAahyjb&|+$z9%xRALLMzT>#=#dYR_ z^?A#dttR$c9O@r@MqcNYAZG~9whaZsha!IDZoqSJ)>;p+ssT<(F`^|Db@`H3WJ!0R zwXksfb7BQ~N~j29m_*H7Yu5{hMN$Jo-ibtaX{2%HCUZ0Kqp|sdpIOj=z{Us6%A08d zJK`vxv}cEv`p9+%HWD1iW*?Ftt6Z*as0>b)-{_<)cOb@`RI`7by8W@|EF>Y}Uyd8I zSif}S+|qZNSg7~=tt+RzQ|-!bY6i?+D#0K{Bk&u{tdhsC`SY$h;lydJqgFHWv#-3q z`2+|#Sx>W(9@xw4k_Kzu8={J?-!E`E?!!gkGa6b|X>3N@&VB@}YUxyGUI=L4zh7() z?k-9<1MsWU7w7oSzEGilXif76_E9Ru3cm5AWD$O`PClXJ{Lx(%WxozVUB$!RBk7BE zi-JbYIFOOH0^tZRgq5ydi+Zpn%Jy6eb?p>+*-Rj7rTfykezy5C(DJsybC~h!3h3Q5 zl{l3lfZxeSfTMT7Vo@bn=UrOVV%xYI>Ur37!JKO3Dl~p;x?kI6;WvM#VLKaY9TW6w z;Q%YoJ5At7r?_{-?n&RK_N~U!T(8Sd%L`Log0uZiRxqhTLpJiwAQAP;>~i;bmfP8{=z>Up`+nV3G}GCog;FU_6LIGoIO5x%>>XbC-xlDl30bS+c*Y1XN=OojGPyxQNV>vwD) zjwh}5S5C}7npd(DlG>3KJc_x=dM(-L5_fLGaOjH^KmKad{HhzuZ-oMtV}a@oHw0-f zuxkIQ2A^FatJ7QS*(@$iD4(n=U#yr-^iyj{YcIGHeRK(Ujd)bK)fV z(vZvM3?7(JA|B(~chkagNDG6pS&zh0{^5YR{K`FtyMbeHoMU(SILT>3Ptbj7Ou4RE za!10^rNsTr@X+2g@8mQ>us&-@ILBvzHfcexfw{(05U`N(Sq}chcytLF@@HU=>|~bz zOQ}Jnnx-zrYtHn`Uy9jBC6ws93X#9o`X@+-Z+9)ntEQ*jkI&H^lK!z&ZIv;2|`&e)L)C!7cC9t)AeU7w$ zl3ZDKFt{T#l}4^z*s5aMKwIVj^VA!6t9j<#Du{Zrv;WkNV#IqiGc(gamB%*GnDGsH zM|3}h1=l)zh>hYAvA~E=4Lkdp${LC03fpQ${bijv=+;F1SYkKcXxMZ*DjmLbqUgV% zX=(ZbM0xeoKtA{n4GS+a%{=ef*E9+%-Kr?rtz@vC48{GM$x^;1ZkZ8?Xu9aTmb-Hb z?IU-gKiLOV_=Td{{<$Tx-yr)*QXd6UK62O|{yF(gnyc8<{=^;0op5PV;J;GzPx37GeoI<9?X4Ta<#^H|)ExP(Q0T>rvp~b^r`(Vy zIG@37yc3!ul5#w7#7k`?=j)Z54;W`{7hHd*ha>KGA&d^@9`>W({hH>}~wMFT#W!os~tbn5kukTT?s*Xjj!w|OHvu2XB z-nEZL_s;i`A%WF|j`U8ar>EK0PePNmA&qGkg)ez@c?fnRr}Sw?)ev{Cw)~h{2i~2H;i)?J951JPMm{p{~&@47ui6&JLnPG z@=G7V&|LN_AHAJS;laAL!xRHW<-qkQ3e00l7Uo^4Q9Zq8V%%O4a>(W#L<2m|(%JG- z2|XJ+NxiU$e7I6BF@VxosVF{NG)50UC>>R?#^JXvPQ{|h&i*zt)>YXemkGjh4qc8_ z@%^6f_{3>%uD)^(FQmr)MxUO7_b4XqxKH8CM;orUZJCL|$tg-7!Z#8t&fzW?ei)H~ z^a28bXG&XE>xDgsN}&SBSA1^->Q_ zKj+hi!;ZnGlsty0P2LGy>l~B`MFeI*`_iGmKel2?gr1xz_3T-SYB-QLV#+1C%6STy=zY|{&@|ZmuYkiQ;z;n zIAM6U{oMAc#`zR%YBN3c8GIAhOB-gw*byd*^j9*NfA2Z*)jg>KD=zj}I;|Fi>qn5jP6cw-%LZBHC;HY?wwn$%t84o{k0GHGA2Wp}n{AYn&$f z5PxMC0&#U%yMkR%5$b)&XaRw64vQDum8uDXsbCk1P$c)xkeR%jOWBNn5Z`fN-E=|9|yoI9lX*k@+9-=$>qr6wj-OwSD`5l=Y8>!%Y-( zL%Gwfmfv2QCx<2^CKi8F>$u@U-Rlr1mGB`$Me-$VoH7U#OT=WIQ2u>sEhWMB9fZpg zlx>>|{<37kf_c9Sg(*WKGh5W3AE{vfGLj~xD-zdY^3vg@+wqJ)*uT*uasJkroOgBV zwUgN41Qh2opAe0GuRUeH~WK7M+Y4;{F%;$EO>pkA7TUj*sWvN=(*oi5|$ zj~&d}ug+c;j%Xw)^t?ZYM6$nOHJe|fEALVIHpnJy28k4i;uZ$emBrO%AE766X#5*h zkcouHL_p;&zW^BU`2ygIX{vw7Tla7`qzc}10o)O%kd?KQTCGa+X(-M2K%#dM@%jg* zc-m)ssO3?39RA2oJ41X1K-ht@dA`uWB#+bg7%=%CV*pI=i;T@m{NkqJIf8o4%`k0a9>ww5lO{@{s;*Xnq*Y{dASs+#r^`}0;xy- z9eorNWF-G8!@Ih=KGFTM0_r1njT9Lfm;!7Ed1FnihHnGrNU>|I;7(D?!F{P8``Ac! zJeitd{w(K>goNGW$t7evQF<>e-7kYKgFar=H6KS*TA;@pvmA=`OK1^T$K+&I^(ilj z;{IB3DW&8e{T0!*bA6pyWxF-&)YID3Gi<+>D*+Dh9~VwIgS{_XmhiFkT3Jr~#$_rn zoOx`=-jOK8SlR^-;7;S~l@Hi5t9b;bz>;<{~8)7d_X5(1Xw*8y80yKZr@)zUGOamO!2J z-JIKn`M0;KqB6i%1#wcxEVIdnfdxG$<~u#^<^zM@w-YC-^1a=lqZkexuauhuwe5<0 z?K+#yU$cKvgfOSaCG85nmQ=`H)`ZXtL)~vZnQNaOEmGIpo-VN3 zr`^*gD}X+TDdD$W-ighn;$J0!e@&)F#mVd;64b*+&*IFw0fW|^vmXMEK#fGoKL61G zBcW{+k@XLH5bp7G0mob?^Ygc zwRp#z`gl|^aTG^*nQu$d3=?-TBH>k02?nR0iC%0@JwNQn1MW_`zyDlF5|1qK@Xt{b zur}q5SvuLJ3TaLnCVjE-K8j5kV7iqWsi7n{+^&9+#?tqIWO*jQQuHo2w9IxIKrapd zniLriY|ngw{cM*f|GC7cH74T-)YfnYRC>4 zFWZsEW9)8U}Yk#SnTK@oQm#=Xwu(-;-5Rr*g zJSOP4;3r!jEXTPI>Dy!q0z#Y9A9=(?J5qZo&3>%q8z!=yzPt=6i-9ZZj$$hjSe@9x zi&$5sd%6I(N3yWVd_4VdM^)D(`T7tzXhvVE#E?FIFbB)vcNs6wh;UN=@(cE z{)GFM(!{f;vc_xfAmXd+dZtCWLIM{j7a!NTNIeID%LnwPwVwrP5`3{=aCs>my?pzg z2{bByOgzINf>(JHz;#^uoWq-W(50JgB}s%WS6)r&rF553n0LRw^L|joo#UT8#Ewhp zg=9AN02#_w-Y%AW&#;lmeRqA2DLKbWW+~+O#jW~EFmW5cGVQVW;*{Pbud5bGVm_Ko zh=8(+KT4*4VvC=^is+P(@$f%Jb;UWJVGEiuD(S5EVyhk}Jbl~84v?f#%#z^!7Im#o zuTff%eq-b=XtkXEf?+{ct%`*Mwl|Q9Q=pveYQC~BRpYi3m__|dpn3JK|As2yDi8Yi zqJ^OR>xr}Volb<4?=%1%{{Irta==E83)`TS(X3z6MApk;$T$Cloh4cJdq))UQ$^OJ z2U8>0mT$ImxOAa8MiGaL4q^3`4sCN0$J3?9Qe6HFDnC?b-aB@$_h2oX&4z!#xHyXy z0z$ZR1=kXH-j<9R4IepM+h&PdhdC=r>h6nq+dcBg7awPW58;#?N;w>hvr5ahDRTai$E={zFmckDRYS zN}ophdz4n>?Y5LjY00A=($X1)yk&t=fb$_u-u8=9ZUINe6eGyA_<9HBCG@8}%klMA zfB#HrkAm1U>2$c@uSgeV>Kgubf@shw!<##iOTWd%|^f z6&U+DXVA<)0=ep0Af?!VkbwSIn9g>S7H{LuXM)VJNv$3NOHT|=3%nZ+ zgZ{aRJ$`mk2;3os)mai+9-fh-c}hd`l}q}Cy?nQr!vcvrbX1VtI!Q!q>(hdhqYX!T zlDOaND4|#hB>NTx3%O)e_6HXy3ZTc5ou^7PL8mkaXhnW4Ggm$>@TK%*prS3Mcj%ci zrULA@zVd3CS z{Ta^}@v#tGB+96OS;OHBIDxlL;8;^SR_I_x1WIBrF(WXqMCN+Ad$XHkOKN)r-(ua| zbbRQ?Lx}$JM^7uc@fi&^nMebi0o-o{5qSdbABZ^su%TEEwOWjozyaP1dsljaPI*g+ zg5|eUwxc`FS6K@VF-w(hR4;ePHbhL2OIaF6TDB!8Tp zXez>nc|?WFLTD{%c3Prbabf2_nE?r0Fz-laEv^gG`#JR3P869#@f72FP`%2pyYsml z*T!pIG#Lq5B;Jys!%fnUjHCkbopih8(6kFP-FR}+TncxsG3gD$mh1|bW3;Z5N@VB~ zA-_-Ou7;NZ+33+6q@ykm>JIl*(p$cWS79L-X-Qjux^fm#l5j{ta6^%h{|a#aUkE7V z`F{c|f_K?YuS0H^r(^S6e00}k#w(^o&Wm3bT)hba=#oxX^#LuEg5)pK?${P|sx10w zTf7U7H1PxO1~tQmh?F<`EsS?xOspw6zP1jI-s3nqN_>Z&tdg^Y9{rIU_+BCT$)!o* z*Y?+}z`wL1Q}9)?fh3(YTa36q2HNnH55@&?k=4T=pbhx@B_wE2>{_zA8uqwgZ%Mc^ zz!&=7R@D~9p5aTz?(tUWJvvO{Qqo(c2 zD!+J0B1&RBpj7_u^lk#|OnQ5(CSmo?@)_H>q}!EnZA8e^(Ru{-I&g+>#my*|Ibi~? zUFH`}!I5)zK%K_39M<2@$#z0Bu@DzMyaIj}BMk*$Tf9^A06^WYf(2l5Ia7>QE-*|q zFepgQ?&{%$s?z_@7t`7os)op&Pv-BoMU}2|qNAfj?IV3WB}e7;4GL7zU85-}bO#GF zou)`0uZ{MFgQU%twbSe({$mu*dsxn(RXE=H}+X z8$C#yXg@K@cr|Nf`|o`R3De_|0JpjJdl!-KhGNg0FxJ(B8{)!Vb}i-bd8;v%#$At+ z>CMrmF_Pq@^a-VFw09emOO|B6491ZqgN8gM@L4(hBIKN{(Gj>ycWNcYnm$+*x-pbd zTA^AiHC(GP!O5B)vL0G34#X+w)(MEEw}`rIP{KLr2v&YlExD&xx7FL8Xf;(jq$m** z`uc#8AW@-qYKr$!@-1=;J1O%YIbBxt9=9`3GhQG28@Ua56yl0uh+Uq$hn!@wpnqh^ zs&aE2#gO7m2VP30^|Fe-lPq9N9hd%ISt>R`s`3=*l+?#oaDvVGbM zbKC_kn8Bxa94xaO=bTxocLD8scLnzwY~$MH-K`nvrB;=4K`R(0I zQHJF;1TD0F2;L-t*rgbI{30U@%R{|`U2)%=55zf1cJlkt7%;tSScE6H| zhQwddJxx@&0N>yE( ze$9?=B)wD50c&Z|HI6|Bt)-Xyt!;m!pAWF$wh|Qx2*2}ZNNMVwR9pc#_1ypJy7>;N z3nx*xGp-hKcP|`2a$~LGy@b9X*fo~ITR$-Ne)rK}>YkLp}rB z!_v=NUC8A_k_06O_eVb6@tS^sD`$SEp>`$x#8{2#$mvwp>;4rMx$!5XlDauQu$FL~ z8dOXX;E$EO=*5{4!ub#p3IdV_g_7U5GKHe5|+7rEFeoJ}gyenss_H{mb=< zSQ{C&!}R+W&qf3G3!P%*Y@e6x;Rox0T#6?(@o+E!1`akZ2^K6wa_Rw4U}fzY-@`8O zri{uPp)y|y9EM&By%HHTbJMzb)0`KJE3{Qag!r ztN;u&K)hay9;*E^0sIPOR)-ZRC)7_3%Z9PYp}L#F1vq0_+T9y7PN*5Awi|B1AW#Z# z5Z2RhoINUS_p}@GD+)_PjWHlh>03C#Hv-5CA*`-HzPs{S6AXnmCBQ)y@K53h`*^z1 zrW8KX60Fa?#16t7__85RnKSk}s9kUISEXzJ9DB;!RQTZLOpz+O_R0d}(d!Ez&{Rqv z8_$XC{aTMBshUgR`K){kkUdDDmKy*=u4TjcD(cI+snRvJL*yyv$gO55yuw=Nh2N9gkX%t?%aT;p zTk;tKH};*>YryQAqE8Z;m3yXgxNE_hlwbZL;ad(H}PJu$v;_edMwYa+kcW5c@?pEB3Yk@#aLp--blyHDQqaG}K-IB37|qv4X^Z~`+31~$ctxk{A~y*-WX z>O5;Y#_4{z=JRlr=Ck3f5bm5|s&|~K@(~v&8!x~E&*q86+H?}#nZNV_<%fRc?kO1~ z87i!tU~;)$D4U~M+PZJi9;AVBqa(Z}EA~wgaN)kg;f6ADkUWZm73hq5=E^k`V&eD; zTE#ucrowMZg$1Q&>Y2R70NJdQanc?Uw(&TV=xS8manwx|(y{o%Ynd5|oZm+X)x(w& zrilk`Ml+tteh!z5=8rew3S3UnA3&_cRci?b6r@IPX}=~tP>T!U75abw^70WnmcW)( zjD^Fe#k<;-41ta+X;)$@t$)lV-0XXO15d^l6mVV~$av7<;Vn?D5Z=+`pq^DRCmcSx z1_W&+#Sp?qI#bSZ!pB|ke59VP8noD=Fr-DktM2PwEX>VuSiqVIM4=be!?i^DB2#U! z7wD>CvgC_b^y(^%#%VGLxy2BRcZVAU&X9wTHcwVF6g=Y!;3sSMVP{{hG@Ec&QZt}o zAXppw|iai=udKvg@DFrKPDd84QpwPxU z@Jdk|o`iBc!+f&>mpO~r(x}=mMuuG%LKn5>LtX) zSHzAbmx+Uo&N>#sBHg_q$4Rt*NIPXV_f4Cjsnp<3sEPR9b32kazKrlg$exHJ^?!}R zgcI-6=42(Y0(>OsR~pd(E5DZKY{%lhey#HI&;0=#!DJ@vcBb8VhS5r(@6#$2kyM$r}ElZ?{^f;bW*jc?&RXXb8t0|Ff z`QU@yQ6{N*cspQKe3(U*a}Gs@HdLadh?o#h!9VwRJ;;paeA`zO`EqME`Q=hB>C?Kd`TbrYg(+cc zQQ#3sQ^7|F+>p!0lM%C@tknxXScttquL|pv1}Dmq{gfhI;+V2xq)4)N5?cerOm)#tOX>)$jMGN|`((X^{*PyDd{gmCd$sCT^ z0{T4G&4Oa#%5sxhV1jTSYPOIk+uY&F$@8Yl`}@6BhB03fw6kHmFo)k(2%q*UK_Fm+ z*ENVx>v;2Jhmc%vU)_PP?>(6ksK(P-l`597ys4aJwhliJ9=2&~B_QJHXpA}=%d0lz z*>xs!9kmz2Q739~(b1);;vy0ulIfK9D&Kkr;?5*U4_pk)&L<;Bt2Q<{o~Ii-t7h+M zkk5Kv_56j`yUGU|*+2lB-MmL9`hHqFG=VJGMPKFLuN|UO)~GV&I|a+;TgGW^WB2#> z*OFra`N}5OVMCx)I?R1lkLpLP$PSF(QWB>0)S=|iMo*u*Q`WoiYER-G<09_Nta%!% zoe5jm3gRi}Zq`yNl`W;AT?_N?n>WrmEG(6iP-1gJj>?pPv#5Zz!#G*2^u;sLUGw2T zdk9TwUqk#p4h1(dEoRR3d$-LrWy#`*S>$|!Uk%gT)+XZfJ-RGO@Vk~=r@I|e1I-_K zJG6<0XEQ>t@3nTN=ec_8wE!KrJ1Ym%Isk5{EdTj8#%1JNhlS5Z-H6x%Z% z3c5}AGl-y>KZO(=_3fk~_G+VI50UI3#0sy91Z~Kc(~nV&k`#2%`*q|HCogvGmd)K7%AtO}gf#Zy((^P4P*78N)9V;+LTxnH$%XX)N6bar5k zu)E(lSJe2JiqW0l-8+*eE$xha)S+02ShsD}F6z2v>gSJ#s$hDrYf5m*JDTmW65Wn# zO0*g|oWOzI%K`Hospk?{Ie*%==1oFz* z>hp9S5mbwu`RT}RD5K#-T4WNj*VVRW9pfLDgY6$y#8;(n^d4E;%xX8bVP{JQhX!X!Gy^@V@1n`h}8Bn zTe=@7EM=?!(@C3&zf5>IRA_*8t;0_r>uRG;-+v$7L8zbF9Gb{1-(liicZIa%xYcsujGeyNM0#m;9Q6ukNNNI+v- zm*w*w9-bN*k;GqTA1d;8+HnU~y7#J}%5iFylejtPNNNLD4ar&$T#o;ytxs@&Q5Z^;A^^zZwrly`-i@s5t7-HEK^8{PNRfzRb0V@d^Tfv5xh$QT@GjA-OQ z2|5)O-^cY8zMOO?olEr{lYAn*0QDIivuU~?RY!IyRZXxg2wwAfGJi$Ka`5+t+E{ET zWe~D0COeogCPi*v{~Z*~ku(g9VnP3>zV$NS7*9&zi}fpZO}|qn?Zq(4nq!=4Mk=t5;?LfPm*ozwS9xECFH@TyEfK66zi-#!t+MHpN zht_zsbY{k0PZf+q@IP+}UgLTC<$#6Fm5*j1IIOJnD>yd;TT`u0c`MTAW~r9_REbVF+O@*47qbH*Yy5GuiNLz`17n85>d zJ>Qi~71zt0m37f%ao!{uah+I3sXeL>Ke;I7B2&oP61*1~2;%6i+4SrZE*!5b6T8u$ zJM!cb=1U2<>!kn6EQ&d^~!J{V{dPW2Yb#auV3 zs;}z>G9lLl9kHMt8w&jkxSrHqMLU_EOqyD;Ee%aF_1bWR%xUgxNYx@4g%5c6*O`~W zCt(0CotF8OQf?+xksa5BoeruD8Q5TstGjQVE}c`wrbNluvA-C@)xa^cWgbk9m=D zzDBj@H67sXgVhwk=1Sg?n2jtRir1$E>EguNfzSLICVoe*&_Ubw!au!$6T{6;`+$I#zAXH_R$PVM zTH+~24xve%RP97r6aQl*mbSPvN!o5Ydp;gZ_7`_&M1 zmaSB7lYa3fv5Y$7eaPnz{!*zPbD#p6*%qZ;V_vFM;zxk7bSS1maQk?eeu6^#3hF@Z zQk&~^!Yw`3qe-7}j)?)$d^0+z>l2mo##7UPXN?suUuCG}3~p<{CaDltO7f@2WE19; zc!<0O64X8k=zY5cfwn>Ugeg%M(IIEFb1bwwk<^k+)b)OyM3qkQX~?3c2nPMg54LC1 z*m@1DC<`=~G(oppf?1 zL~c+%ahs^!&E4Ib3^e;kb+*yt&s^QMU+Io^f~_gu#bTy?pW$wZ`{#b=du5m!2LU;P zIaA1d(t+F3C-n1!0-lWSJqB*jvxz`nx5d4(`<>hMk@VM7Z@bC-%j* z#>Uz+CYX*5)~aPt^VT^onL|Eo^ymlDN*2r@Sz3}Ov+1zf!Tn?hZvayt1abtz@Gc?8_4Vo(x4*JPo5n~z%g8mcMYk1mIs7CBg{;_R1wmdI!! znkjE@rp>7)vd3mQu6|-2RQoA>@Q_Toq^GoqT&1Nu;ZV&n$FOq%@Hj-JbDm-=Q(@(0P7CJwvQ+`&2-U{w!&d-S|#soDFwktTlLx(+-(gVm>^myOpHh>5?g($;lBl$dYAr6lQ#i*=6 z$`7;oZI7knx_FDXJKqNH)sxTZ$)Pn&nq*;{tgbyLm9Yt_@MAsyWZM43bk-QH%f{d% z8V$t)ngbVb#Jdha(R5c$k+bN3yHa*f%-XnZKj3OxK=l|9x1NwUT4wX7oMYLiKrkow zS4j)JKJpbBv^(~yxueE_lh1dh6OjJk+mQif| zzIsUucNswwb}gwdPtv0AApfbVvXp5uR$kI$Zf&hgnMW);N4OYAAcpz-SaIb)c&NMB z%%97y3)3f7(yac)@eyb}6ouvPW-_4_c0 zT14sS|8lf@Vx%PeR!i;m=9{^jk1`HX7K&!_SN{ga|1#C#=+>cU{IAp{#Spj3JTuUS ztvC1nh4G9s!M!1$k_!$F{s@*i%}!9;JQPS>4Ed`M67%{JY~o?At$%Y_Jz3oyIN2|k zi4??_=gFP!J9Z`sA1?Z^cSO&l90$l8JhE{;xb#bYJ0fRiNuB9r=CY8aQbu0TFtDz_hS}C zcf$LaqJ@humuBOG|AA$^grQtoqS%O}<1{NTCv(M**Abypvm)0GRuk!3nb^|O=crPC zX}*Cc-4GhdIRQBDY($LBnmQ=tv3*+|cXNo^mmeK*$Vq?sB@EZi4#-lXBGp1C_kGv1aeq%)ZzG^TWKW6;7M5wCR z5Z+;?#V#c>b2wtq#)$KK6|pwdX>aRtbk2O`5su=A8cZHI={D?mQd;;hUF8XS4+oy5xhz=U z5@G09tx};uZD=@z7X6ItC+!bUqt-5o=+B+|5(x2Ayx4eRRJ53NDWah$Gyu;ahRn}G z)qCSD{EGkwX;f>;9E*wpYM?NoLW#sy*5`tsVP85QBII>UPy_tzZHu{qH);jO?Kh9X za5%I(I10X35j3;6zyT#@G!3+tssx0k)JW|;!_^f0vaJgR>OaxN9>8M3Ae*H7H%Ybk z3M|N0_Hfb1f{<+Ab5!ERR4rm9>gnp@lv}N z;1NXLRw-cBBsP$PhEt^FaelJW6h=vCColPo>|2q}*)JxfjC#<*dd38mNo`$B_b^xF zqtFFdmI=(Xl4*Y7n|izor5pLGM%pKzV_i)`hT!4a?R53oR@$opXbbhmD9-R%{AOFW zgTi((qaFj?Z_*#Zk)!(k)$Z+~_}9_zR03*#SLEh z3+`i2U+q*b2J(+yJy3RSOYb zzxcg)ko~kG6PL(sF(}EJJ*fkMdE~IHJEQCmu>JPIaUm2)VnUEtxj$IP{AVA?%Acb~ zFQNWIQl4O{+)+(j%n)+JcvT!nCF>M!Z1`{e@ zD()&NO%7dz5!B3(Em6@JJGQd6>L0R54p#bx#~-So-W^k4RQ?v ztyLt4Qli6X3-V6AUpC8PG%Gp%?9s@P3kpNQsl-VN4I`?8m=LslkD-f`ZZRP-zKx&R6pnDJ%qw%4ANtuJ>srrr6vH>$HD(Cj{uX`(BZi&-MG|u9$(p-TuHKlZ#RX6VSp?u(Fo+aw@P!O6taD$4wh?)Qfah zNBKXCU@sPTopwr;&!5Tfih_w!6SKs>!-D+R0^A4Njx2$`98te3z+0D&?yk0iuW9;8 zvdd6}^I=H@^#Lfn5JCMx+nE=-3C!E8ISB;`vxfGicP}CDHp2@gX;;Fu%xv`DJd8=b z-dw}#M%O`B z2g-C1_}57dNWlFK4Jc)9*R7~Z$+E)VMUm&PRicctAm789#HLuZPcew(Ehu46z%A_{ z<4^@U&&gXHb)iqJiE#O%xT_+5gu|l^!!d`ttmkmtYJ8|T;>-m?-Hlph+@1a+X&~%} zbf78>;Ujzf66=(`pv6i;x~6_TF=?t2Mm^a0K4)qm3S>HH9{e~Ed-W}=4Lou0#lXM5 znHCfsE~aE;Jysoc(FGyX^!-Px$JxW*!u_9%cFR7cdW^Mtwc~7V#MVC1f9_DN&qkE;#AjD}mOZF84M&@G4THYhkeq`a$CdDR$N(!+Jt+~QK{^2!W z;}WNOHWp3CGl};{FZif2ASl-r4kDpaBzTkGmim^yol~vmM3{DoJpXzG*I3kW@SgL* z`0VtIgwD48PuAi8x0ol~T_7qA;e5^RM)x$-2 z$PG3H__+VkG3+Vh{Rl5dk(UV=Va&is+IiMcRXk(EE*4FMO7>ytrR!n3?3edTJ=gTo zCKLz{eAm)5yc1)zDV~(d3)YKc+0$Ja0D*%u3CmD%`c?KQzMb2PZ$s1J7u@$r6x17^ za436?A9u3AmF#~D2yu`@s7a3gH{0%Q7J7Wa?o1xVVoQ>XBm0NbAdq=+a$?98g7jZJ zCTcYHQl(e666;GjF*YK)KuuUUTm(6>D|@vxB0L;y0yPv(mAvciY4E|W^?M=ue~QX5 z7_wE^s|7Dc2_TCxJyRs;rq8`-=wy}Y=zZ#?0dDziM#Wzv%wX*%+Zx$%PC;bup_=W_ zSDaqrn%SdBk; z@NYPX3kSbj%0WOEK1>DcYiVyUC{E=CEk8}tPAK_I%QsXS<~iy1WF8o^SJP+!GUoau z1d>JM%qTd8H>g?3H;i3v-epc&@XlgI$)L))Dc~=k>#pk!2BJJOu7$7-8b>=uz1d)Z`G(?mFSSI*`6Y-`Bci7#5lHP7Kgbbg4!IDR;7O7r)kl&!V%P&A3b z-?iN=vHj!_DlZk79ueBMkh3q@sk;Gh9Dz{j_F!SjF1K?a{ra2lC0?rFCuA6Q!RK^1 z_GuD0Qt3Z-E^IE}U-P>?7LcQ45`6CXByZQc9ls`t({g=zeFob6x1JX8a#v@0F)cK= z*|0MaD#J)RWvp&;xhMYrh7o^09A%`gQU7NH=gb-jtE!>AReSP_7*RRS<07CQG}C5| zzAv3!U(Ca})2BWG8}=PBScSqjb>wDrT zs+ZYij{A+$K(t=gWaI)&{M0i;6%vTEK!epoF84C(7WxDbvl23JOnvIT34+J{?v&y~ zRtC`QxgFGR?wTtpUknqHqO1wb7yg$YquHEOWMSu2fe^z@J zaeFA7sEF5r4|Z8^CjWjs3*b0z)_5|KCOPW333wyE9Utt}sN1xkzE=X~;oCKIg)qM` zRk!SJQ5xm&BgONIM)<`<-!Oh)2T4AaWD(~JXLRrGWLZ}H7Vdr51QPv(7a}x3hMf$LE7Ds1EYvs-QH2Hc{pJoN%5HOmDhB`o`21 zceGoMiapocr?eQT{pNeiUp&swX+0eKFTPwwhuB9(Cb6{>>z;qlp?*W{T@v<3q??5tEvmfKU}O8|{?;Fw zyJkkou8wae_EPtnUGvS?puZcB;NuKsCKn(`xv8h`rYC-}_vA7}IFSjfm7$=kg7tAB zZ~dtL&>ekzNnqbG|G2Vt-1htUpTuej9+L2F?t82rZ_U6YK<@`N-tyrW;m@+t7UF`qqgKX6) zl)RL^VllasLR5QbChnHxzDE|VfmPfNi1LATOeBX%5$jc+Lmy}V{w0Z<(d~c0fm0{T zWUpnIDqljbHl^-47JbvR>OWXSq8%&*%=JiH#<@-*Yt|XsGdcW1-Fg$>O$!9^b;k%- z%Dum4UXT2AqwS1#)TRCK*FWB=ZT{DeOf%^DQ-OlXRoLT=5XGr*?vpO&Sor>Va5o)v zk<2|~t(s0^(<%n1L2jd3I|RJR_{JDJL1;5GEc#&>&Y(s`gzanb0d%GjyH;olg@$RL ztd{=v^8~2s=*CUFHAS-L+_9*>u$gG)L73;SK_Bkabf-@zOq+&lAj=E~(~m@+02~>u z=%>!!teMwEVpKtzw1*tYffw2a)NUGm-;0kBk0!i zd80~zJS89a)TIf+BU~p4U5sf*pU~rRZ~l#@&K{-iqgxAF4FI~Vxmq?bT42(1Z9qxg zYUf4$Wm}OYJ1xM3GMFV+Y8(W`(gF188&7RB86AVP+I0dhoiaSwgFRgn2iSxVK{iEs zU6w0LN&mFrB?fJwn43>YQ)%BhUvA+_e#+e6Mm*8sHsFSJw&S9@StxoMeIH7|Up?b+uZ@@?36{?4w8UR$HYK|vleRwbYHz{CyBR^I8 z?}~rCeol#?U5Ox%iikD21F_p*7O*^?07sQ{|Mp&D=@`eL8qdLpmmhQq@2GB9jSlA^yQtC{k>=rFWK7M+Zw>%lI&RwEd-e>6|noBr zUQTo*%C&kMV%rMNuTbhS%7S0JR z*e(p?OZd3CIJhk0;|RRJNtKvKYbVCQ#WITh0pve>uDkX)-IyQiEpASo)W9@Jm^2Hp zWCfn#Sl#mcb8@pi-G}6MN9Z9=EWpdM{`;O}A4(5U`o>cd^6=Hez<=8KJw5*XsdY1c zZQKezK)A68X@LM52r^5IyH91L@l;Gm{-`L?GX~VcvXvUZuvvJHy>v^3(>iwj8%1tf zHNMYI(H@D$@uB((*Kcjpc-4sq?T3UPp8b_gm*Ap4$#B31GH;oQ=SKUs%mMB>!6M|S3~a7W|7tSoFqNee zG�=%aO0*y|C?1dHUr%xQV-C*F0ysTzV{sR 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 + dtlsServerStatus->setIcon(QIcon(DTLSRXICON)); connect(dtlsServerStatus, SIGNAL(clicked()), this, SLOT(toggleDTLSServer())); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index d9de537f..ff7a4d60 100755 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -248,8 +248,8 @@ DTLS - - ../../../עבודה/dtls_logo/tx_dtls.png../../../עבודה/dtls_logo/tx_dtls.png + + :/icons/tx_dtls.png:/icons/tx_dtls.png diff --git a/src/packet.cpp b/src/packet.cpp index f23cf959..ffde06e0 100755 --- a/src/packet.cpp +++ b/src/packet.cpp @@ -303,10 +303,10 @@ QIcon Packet::getIcon() if (isDTLS()) { if (fromIP.toUpper().contains("YOU")) { - QIcon myIcon(UDPSENDICON); + QIcon myIcon(DTLSSENDICON); return myIcon; } else { - QIcon myIcon(UDPRXICON); + QIcon myIcon(DTLSRXICON); return myIcon; } diff --git a/src/packetsender.qrc b/src/packetsender.qrc index ff269300..4e776e5c 100755 --- a/src/packetsender.qrc +++ b/src/packetsender.qrc @@ -52,5 +52,7 @@ packetsender.css iris_and_marigold.jpg ps_panels.json + icons/tx_dtls.png + icons/rx_dtls.png From 2c651f7a01ee15228508ab1a2a5070aedc1a624b Mon Sep 17 00:00:00 2001 From: israel Date: Mon, 18 Dec 2023 16:32:37 +0200 Subject: [PATCH 66/79] added gui changes and Two Side Of Verification checkBox --- src/dtlsserver.cpp | 17 ++- src/dtlsserver.h | 2 +- src/mainwindow.cpp | 22 +++- src/mainwindow.h | 3 +- src/mainwindow.ui | 281 +++++++++++++++++++++++------------------- src/packetnetwork.cpp | 21 +++- src/packetnetwork.h | 1 + 7 files changed, 218 insertions(+), 129 deletions(-) diff --git a/src/dtlsserver.cpp b/src/dtlsserver.cpp index fc5c6896..0a848a79 100644 --- a/src/dtlsserver.cpp +++ b/src/dtlsserver.cpp @@ -36,21 +36,31 @@ QString connection_info(QDtls *connection) //! [1] DtlsServer::DtlsServer() { + QSettings settings(SETTINGSFILE, QSettings::IniFormat); + // QFile caCertFile("C:/rsa_encryption/ca-signed-cert/signed-cert.pem"); + // QFile keyFile("C:/rsa_encryption/server-key.pem"); + // QFile certFile("C:/rsa_encryption/server-signed-cert.pem"); connect(&serverSocket, &QAbstractSocket::readyRead, this, &DtlsServer::readyRead); ///////////////////////////////////////////////////// QFile certFile("C:/Users/israe/OneDrive - ort braude college of engineering/rsa_encryption/server-signed-cert.pem"); + //QFile certFile("C:/rsa_encryption/server-signed-cert.pem"); + if(!certFile.open(QIODevice::ReadOnly)){ return; } QSslCertificate certificate(&certFile, QSsl::Pem); QFile keyFile("C:/Users/israe/OneDrive - ort braude college of engineering/rsa_encryption/server-key.pem"); + //QFile keyFile("C:/rsa_encryption/server-key.pem"); + if(!keyFile.open(QIODevice::ReadOnly)){ return; } QSslKey privateKey(&keyFile, QSsl::Rsa); // Or QSsl::Ec if your key is ECDSA QFile caCertFile("C:/Users/israe/OneDrive - ort braude college of engineering/rsa_encryption/ca-signed-cert/signed-cert.pem"); + //QFile caCertFile("C:/rsa_encryption/ca-signed-cert/signed-cert.pem"); + if(!caCertFile.open(QIODevice::ReadOnly)){ return; } @@ -60,7 +70,12 @@ DtlsServer::DtlsServer() serverConfiguration.setLocalCertificate(certificate); serverConfiguration.setPrivateKey(privateKey); serverConfiguration.setCaCertificates(QList() << caCertificate); - serverConfiguration.setPeerVerifyMode(QSslSocket::VerifyPeer); + if(settings.value("twoVerify").toString() == "true"){ + serverConfiguration.setPeerVerifyMode(QSslSocket::VerifyPeer); + } else{ + serverConfiguration.setPeerVerifyMode(QSslSocket::VerifyNone); + } + /////////////////////////////////////////////////////////////////// //serverConfiguration = QSslConfiguration::defaultDtlsConfiguration(); diff --git a/src/dtlsserver.h b/src/dtlsserver.h index 16db6cbd..8d198e5b 100644 --- a/src/dtlsserver.h +++ b/src/dtlsserver.h @@ -17,6 +17,7 @@ class DtlsServer : public QObject Q_OBJECT public: + QSslConfiguration serverConfiguration; DtlsServer(); ~DtlsServer(); @@ -73,7 +74,6 @@ public slots: bool listening = false; - QSslConfiguration serverConfiguration; QDtlsClientVerifier cookieSender; std::vector> knownClients; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index c0d01aa6..96165923 100755 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -73,10 +73,13 @@ MainWindow::MainWindow(QWidget *parent) : { ui->setupUi(this); + + QCheckBox* leaveSessionOpen; + QCheckBox* twoVerify; QSettings settings(SETTINGSFILE, QSettings::IniFormat); - + //leaveSessionOpen if(settings.value("leaveSessionOpen").toString() == "false"){ ui->leaveSessionOpen->setChecked(false); } else { @@ -86,6 +89,16 @@ MainWindow::MainWindow(QWidget *parent) : leaveSessionOpen = ui->leaveSessionOpen; connect(leaveSessionOpen, &QCheckBox::toggled, this, &MainWindow::on_leaveSessionOpen_StateChanged); + //twoVerify + if(settings.value("twoVerify").toString() == "false"){ + ui->twoVerify->setChecked(false); + } else { + ui->twoVerify->setChecked(true); + } + + twoVerify = ui->twoVerify; + connect(twoVerify, &QCheckBox::toggled, &packetNetwork , &PacketNetwork::on_twoVerify_StateChanged); + cipherCb = ui->cipherCb; //add the combobox the correct cipher suites QList ciphers = QSslConfiguration::supportedCiphers(); @@ -95,6 +108,7 @@ MainWindow::MainWindow(QWidget *parent) : if ( ui->udptcpComboBox->currentText().toLower() != "dtls"){ ui->leaveSessionOpen->hide(); + ui->twoVerify->hide(); cipherCb->hide(); ui->CipherLable->hide(); } @@ -2309,6 +2323,8 @@ void MainWindow::on_actionHelp_triggered() QDesktopServices::openUrl(QUrl("https://packetsender.com/documentation")); } + + void MainWindow::on_leaveSessionOpen_StateChanged(){ //ui.checkBox->setChecked(checkBoxState); @@ -2695,10 +2711,14 @@ void MainWindow::on_udptcpComboBox_currentIndexChanged(const QString &arg1) ui->leaveSessionOpen->show(); cipherCb->show(); ui->CipherLable->show(); + ui->twoVerify->show(); + } else { ui->leaveSessionOpen->hide(); cipherCb->hide(); ui->CipherLable->hide(); + ui->twoVerify->hide(); + } diff --git a/src/mainwindow.h b/src/mainwindow.h index 48094680..81f7298c 100755 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -41,7 +41,7 @@ class PreviewFilter : public QObject Q_OBJECT public: - QCheckBox* leaveSessionOpen; + explicit PreviewFilter(QObject * parent, QLineEdit * asciiEdit, QLineEdit * hexEdit) : QObject{parent}, asciiEdit{asciiEdit}, hexEdit{hexEdit} @@ -97,6 +97,7 @@ class MainWindow : public QMainWindow void sendPacket(Packet sendpacket); public slots: + //void on_twoVerify_StateChanged(); void on_sendSimpleAck_StateChanged(); void on_leaveSessionOpen_StateChanged(); void toTrafficLog(Packet logPacket); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index ff7a4d60..920945e6 100755 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -323,7 +323,7 @@ true - + 0 0 @@ -334,136 +334,169 @@ 16777215 - + - 660 + 0 0 - 191 - 24 + 701 + 31 - - - AES256-GCM-SHA384 - - - - - AES128-GCM-SHA256 - - - - - AES256-GCM-SHA384 - - - - - AES128-GCM-SHA256 - - - - - AES128-SHA256 - - - - - AES256-SHA384 - - - - - AES128-SHA + + + QLayout::SetMinAndMaxSize - - - - AES256-SHA + + 0 - - - - CHACHA20-POLY1305-SHA256 - - - - - RC4-MD5 - - - - - RC4-SHA - - - - - CAMELLIA128-SHA256 - - - - - CAMELLIA256-SHA - - - - - ECDHE-RSA-AES128-GCM-SHA256 - - - - - ECDHE-RSA-AES256-GCM-SHA384 - - - - - ECDHE-ECDSA-AES128-GCM-SHA256 - - - - - ECDHE-ECDSA-AES256-GCM-SHA384 - - - - - DHE-RSA-AES128-GCM-SHA256 - - - - - DHE-RSA-AES256-GCM-SHA384 - - - - - - - 570 - 0 - 81 - 20 - - - - Cipher Suites: - - - - - - 430 - 0 - 111 - 22 - - - - Persistent DTLS - + + + + Cipher Suites: + + + + + + + + AES256-GCM-SHA384 + + + + + AES128-GCM-SHA256 + + + + + AES256-GCM-SHA384 + + + + + AES128-GCM-SHA256 + + + + + AES128-SHA256 + + + + + AES256-SHA384 + + + + + AES128-SHA + + + + + AES256-SHA + + + + + CHACHA20-POLY1305-SHA256 + + + + + RC4-MD5 + + + + + RC4-SHA + + + + + CAMELLIA128-SHA256 + + + + + CAMELLIA256-SHA + + + + + ECDHE-RSA-AES128-GCM-SHA256 + + + + + ECDHE-RSA-AES256-GCM-SHA384 + + + + + ECDHE-ECDSA-AES128-GCM-SHA256 + + + + + ECDHE-ECDSA-AES256-GCM-SHA384 + + + + + DHE-RSA-AES128-GCM-SHA256 + + + + + DHE-RSA-AES256-GCM-SHA384 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Persistent DTLS + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Two Sides Of Verification + + + + diff --git a/src/packetnetwork.cpp b/src/packetnetwork.cpp index e17b7e9f..76f2ae64 100755 --- a/src/packetnetwork.cpp +++ b/src/packetnetwork.cpp @@ -1003,7 +1003,7 @@ void PacketNetwork::packetToSend(Packet sendpacket) QTimer* timer = new QTimer(this); thread->timer = timer; connect(timer, SIGNAL(timeout()), thread, SLOT(onTimeout())); - timer->start(500); + timer->start(2000); } dtlsthreadList.append(thread); @@ -1285,6 +1285,25 @@ std::vector PacketNetwork::getCmdInput(Packet sendpacket, QSettings& se return cmdComponents; } + + + +void PacketNetwork::on_twoVerify_StateChanged(){ + //ui.checkBox->setChecked(checkBoxState); + + QSettings settings(SETTINGSFILE, QSettings::IniFormat); + QString twoVerify = settings.value("twoVerify", "false").toString(); + if(twoVerify == "false"){ + settings.setValue("twoVerify", "true"); + dtlsServer.serverConfiguration.setPeerVerifyMode(QSslSocket::VerifyPeer); + } + else{ + settings.setValue("twoVerify", "false"); + dtlsServer.serverConfiguration.setPeerVerifyMode(QSslSocket::VerifyNone); + + } +} + //void PacketNetwork::addServerResponse(const QString &clientAddress, const QByteArray &datagram, const QByteArray &plainText, QHostAddress serverAddress, quint16 serverPort, quint16 userPort) //{ // //ned to fix the "to port" field diff --git a/src/packetnetwork.h b/src/packetnetwork.h index ee37e092..b79dc7e2 100755 --- a/src/packetnetwork.h +++ b/src/packetnetwork.h @@ -117,6 +117,7 @@ public slots: void readPendingDatagrams(); void disconnected(); void packetToSend(Packet sendpacket); + void on_twoVerify_StateChanged(); private slots: From 0675dfe985ba667c3849a82bc78740e7f22ca1a1 Mon Sep 17 00:00:00 2001 From: israel Date: Wed, 20 Dec 2023 13:59:12 +0200 Subject: [PATCH 67/79] the server takes his certs from the settings window but its loading only once, and loading proccess extracted --- src/dtlsserver.cpp | 113 ++++++++++++++++++++++++++++++--------------- src/dtlsserver.h | 6 +++ src/mainwindow.ui | 2 +- 3 files changed, 82 insertions(+), 39 deletions(-) diff --git a/src/dtlsserver.cpp b/src/dtlsserver.cpp index 0a848a79..39822eea 100644 --- a/src/dtlsserver.cpp +++ b/src/dtlsserver.cpp @@ -36,52 +36,15 @@ QString connection_info(QDtls *connection) //! [1] DtlsServer::DtlsServer() { - QSettings settings(SETTINGSFILE, QSettings::IniFormat); // QFile caCertFile("C:/rsa_encryption/ca-signed-cert/signed-cert.pem"); // QFile keyFile("C:/rsa_encryption/server-key.pem"); // QFile certFile("C:/rsa_encryption/server-signed-cert.pem"); connect(&serverSocket, &QAbstractSocket::readyRead, this, &DtlsServer::readyRead); - ///////////////////////////////////////////////////// - QFile certFile("C:/Users/israe/OneDrive - ort braude college of engineering/rsa_encryption/server-signed-cert.pem"); - //QFile certFile("C:/rsa_encryption/server-signed-cert.pem"); - - if(!certFile.open(QIODevice::ReadOnly)){ - return; - } - QSslCertificate certificate(&certFile, QSsl::Pem); - - QFile keyFile("C:/Users/israe/OneDrive - ort braude college of engineering/rsa_encryption/server-key.pem"); - //QFile keyFile("C:/rsa_encryption/server-key.pem"); - if(!keyFile.open(QIODevice::ReadOnly)){ - return; - } - QSslKey privateKey(&keyFile, QSsl::Rsa); // Or QSsl::Ec if your key is ECDSA + loadKeyLocalCertCaCert(); - QFile caCertFile("C:/Users/israe/OneDrive - ort braude college of engineering/rsa_encryption/ca-signed-cert/signed-cert.pem"); - //QFile caCertFile("C:/rsa_encryption/ca-signed-cert/signed-cert.pem"); - if(!caCertFile.open(QIODevice::ReadOnly)){ - return; - } - QSslCertificate caCertificate(&caCertFile, QSsl::Pem); - serverConfiguration = QSslConfiguration::defaultDtlsConfiguration(); - serverConfiguration.setLocalCertificate(certificate); - serverConfiguration.setPrivateKey(privateKey); - serverConfiguration.setCaCertificates(QList() << caCertificate); - if(settings.value("twoVerify").toString() == "true"){ - serverConfiguration.setPeerVerifyMode(QSslSocket::VerifyPeer); - } else{ - serverConfiguration.setPeerVerifyMode(QSslSocket::VerifyNone); - } - - /////////////////////////////////////////////////////////////////// - - //serverConfiguration = QSslConfiguration::defaultDtlsConfiguration(); - //serverConfiguration.setPreSharedKeyIdentityHint("Qt DTLS example server"); - //serverConfiguration.setPeerVerifyMode(QSslSocket::VerifyPeer); - //cookieSender.setDtlsConfiguration(dtlsConfiguration); } //! [1] @@ -118,6 +81,7 @@ void DtlsServer::close() void DtlsServer::readyRead() { + //! [3] const qint64 bytesToRead = serverSocket.pendingDatagramSize(); if (bytesToRead <= 0) { @@ -159,6 +123,7 @@ void DtlsServer::readyRead() if ((*client)->isConnectionEncrypted()) { QSettings settings(SETTINGSFILE, QSettings::IniFormat); + //TODO: split into two function, one for decryption and one for writting QDtls * dtlsServer = client->get(); dgram = dtlsServer->decryptDatagram(&serverSocket, dgram); @@ -490,3 +455,75 @@ QHostAddress DtlsServer::resolveDNS(QString hostname) // std::vector infoVect; // infoVect.push_back(); //} + +void DtlsServer::loadKeyLocalCertCaCert(){ + QSettings settings(SETTINGSFILE, QSettings::IniFormat); + settings.value("sslCaPath", SETTINGSPATH + "cert.pem"); + //settings.value("sslLocalCertificatePath", SETTINGSPATH + "cert.pem"); + //settings.value("sslPrivateKeyPath", SETTINGSPATH + "key.pem"); + + QString localCertPath = settings.value("sslLocalCertificatePath", SETTINGSPATH + "cert.pem").toString(); + QFile certFile(localCertPath); + + if(!certFile.open(QIODevice::ReadOnly)){ + return; + } + QSslCertificate currentCertificate(&certFile, QSsl::Pem); + certificate = currentCertificate; + + QString keyPath = settings.value("sslPrivateKeyPath", SETTINGSPATH + "key.pem").toString(); + QFile keyFile(keyPath); + + //QFile keyFile("C:/Users/israe/OneDrive - ort braude college of engineering/rsa_encryption/server-key.pem"); + //QFile keyFile("C:/rsa_encryption/server-key.pem"); + + if(!keyFile.open(QIODevice::ReadOnly)){ + return; + } + QSslKey currentPrivateKey(&keyFile, QSsl::Rsa); // Or QSsl::Ec if your key is ECDSA + privateKey = currentPrivateKey; + + //get the full path to to ca-signed-cert.pem file + QString caCertFolder = settings.value("sslCaPath", SETTINGSPATH + "cert.pem").toString(); + QString fullCaCertPath; + QDir dir(caCertFolder); + if (dir.exists()) { + QStringList nameFilters; + nameFilters << "*.pem"; // Filter for .txt files + + dir.setNameFilters(nameFilters); + QStringList fileList = dir.entryList(); + + if (!fileList.isEmpty()) { + // Select the first file that matches the filter + fullCaCertPath = dir.filePath(fileList.first()); + } else { + qDebug() << "No matching files found."; + } + } else { + qDebug() << "Directory does not exist."; + } + QFile caCertFile(fullCaCertPath); + //QFile caCertFile("C:/Users/israe/OneDrive - ort braude college of engineering/rsa_encryption/ca-signed-cert/signed-cert.pem"); + //QFile caCertFile("C:/rsa_encryption/ca-signed-cert/signed-cert.pem"); + + if(!caCertFile.open(QIODevice::ReadOnly)){ + return; + } + QSslCertificate currentCaCertificate(&caCertFile, QSsl::Pem); + caCertificate = currentCaCertificate; + setConfiguration(); +} + +void DtlsServer::setConfiguration(){ + QSettings settings(SETTINGSFILE, QSettings::IniFormat); + serverConfiguration = QSslConfiguration::defaultDtlsConfiguration(); + serverConfiguration.setLocalCertificate(certificate); + serverConfiguration.setPrivateKey(privateKey); + serverConfiguration.setCaCertificates(QList() << caCertificate); + if(settings.value("twoVerify").toString() == "true"){ + serverConfiguration.setPeerVerifyMode(QSslSocket::VerifyPeer); + } else{ + serverConfiguration.setPeerVerifyMode(QSslSocket::VerifyNone); + } +} diff --git a/src/dtlsserver.h b/src/dtlsserver.h index 8d198e5b..a7f6448b 100644 --- a/src/dtlsserver.h +++ b/src/dtlsserver.h @@ -17,6 +17,10 @@ class DtlsServer : public QObject Q_OBJECT public: + QSslCertificate certificate; + QSslKey privateKey; + QSslCertificate caCertificate; + QSslConfiguration serverConfiguration; DtlsServer(); ~DtlsServer(); @@ -29,6 +33,8 @@ class DtlsServer : public QObject Packet createPacket(const std::vector& packetInfo, const QByteArray& dgram); std::vector createInfoVect(const QHostAddress &fromAddress, quint16 fromPort, const QHostAddress &toAddress, quint16 toPort); bool serverResonse(QDtls* dtlsServer); + void loadKeyLocalCertCaCert(); + void setConfiguration(); QString getIPmode(); bool IPv4Enabled(); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 920945e6..eeca54f0 100755 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -492,7 +492,7 @@ - Two Sides Of Verification + Add Server Verification of The Client From 340e53b72943ca4e8f8801a76723798c5640b8e4 Mon Sep 17 00:00:00 2001 From: israel Date: Thu, 21 Dec 2023 20:33:18 +0200 Subject: [PATCH 68/79] the server can reload its files every time it has been changed --- src/dtlsserver.cpp | 4 ++++ src/dtlsserver.h | 1 + src/mainwindow.h | 2 +- src/settings.cpp | 53 ++++++++++++++++++++++++++++++++++++++++++++++ src/settings.h | 7 +++++- 5 files changed, 65 insertions(+), 2 deletions(-) diff --git a/src/dtlsserver.cpp b/src/dtlsserver.cpp index 39822eea..3942baa5 100644 --- a/src/dtlsserver.cpp +++ b/src/dtlsserver.cpp @@ -527,3 +527,7 @@ void DtlsServer::setConfiguration(){ serverConfiguration.setPeerVerifyMode(QSslSocket::VerifyNone); } } + +void DtlsServer::on_signedCert_textChanged(){ + loadKeyLocalCertCaCert(); +} diff --git a/src/dtlsserver.h b/src/dtlsserver.h index a7f6448b..df5ed897 100644 --- a/src/dtlsserver.h +++ b/src/dtlsserver.h @@ -68,6 +68,7 @@ private slots: void pskRequired(QSslPreSharedKeyAuthenticator *auth); public slots: + void on_signedCert_textChanged(); //void serverReceivedDatagram(const QString& peerInfo, const QByteArray &clientMessage, const QByteArray& dgram); private: diff --git a/src/mainwindow.h b/src/mainwindow.h index 81f7298c..0403fca0 100755 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -75,6 +75,7 @@ class MainWindow : public QMainWindow explicit MainWindow(QWidget *parent = nullptr); ~MainWindow(); + PacketNetwork packetNetwork; QString ASCIITohex(QString &ascii); QString hexToASCII(QString &hex); void loadPacketsTable(); @@ -244,7 +245,6 @@ class MainWindow : public QMainWindow QList packetsSaved; QList packetsRepeat; int stopResending; - PacketNetwork packetNetwork; QNetworkAccessManager * http; QTimer refreshTimer; QTimer slowRefreshTimer; diff --git a/src/settings.cpp b/src/settings.cpp index 036b41ca..489ae808 100755 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -100,6 +100,21 @@ Settings::Settings(QWidget *parent, MainWindow* mw) : ui->multiSendDelayLabel->hide(); ui->multiSendDelayEdit->hide(); + + ////////////////////////////////////////////////////////////////////////////set the initial value of the 3 pathes + initialSslLocalCertificatePath = settings.value("sslLocalCertificatePath", "").toString(); + initialSslCaPath = settings.value("sslCaPath", "").toString(); + initialSslPrivateKeyPath = settings.value("sslPrivateKeyPath", "").toString(); + +////////////////////////////////////////////////connect signal of "settins" to a slot of "mainwindow->packetnetwork->dtlsserver"/////////////////// + //MainWindow *mainWindow = qobject_cast(parent); + MainWindow *mainWindow = dynamic_cast(parent); + //DtlsServer dtlsServer = mainWindow->packetNetwork.dtlsServer; + DtlsServer& dtlsServer = mainWindow->packetNetwork.dtlsServer; + + connect(this, &Settings::loadingCertsAgain, &dtlsServer, &DtlsServer::on_signedCert_textChanged); +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + //connect(sendSimpleAck, &QCheckBox::clicked, this, &MainWindow::on_loadKeyButton_clicked); if(settings.value("sendSimpleAck").toString() == "false"){ @@ -536,6 +551,34 @@ void Settings::on_buttonBox_accepted() ENCODEMACROSAVE(3); ENCODEMACROSAVE(4); ENCODEMACROSAVE(5); + ////////////////////////////////////////////////////////////////////// + /// +// initialSslLocalCertificatePath = settings.value("sslLocalCertificatePath", "").toString(); +// initialSslCaPath = settings.value("sslCaPath", "").toString(); +// initialSslPrivateKeyPath = settings.value("sslPrivateKeyPath", "").toString(); + + if((initialSslLocalCertificatePath != settings.value("sslLocalCertificatePath", "").toString()) || + (initialSslCaPath != settings.value("sslCaPath", "").toString()) || + (initialSslPrivateKeyPath != settings.value("sslPrivateKeyPath", "").toString())){ + + initialSslLocalCertificatePath = settings.value("sslLocalCertificatePath", "").toString(); + initialSslCaPath = settings.value("sslCaPath", "").toString(); + initialSslPrivateKeyPath = settings.value("sslPrivateKeyPath", "").toString(); + emit loadingCertsAgain(); + } + + + + +// ////////////////////////////////////////////////connect signal of "settins" to a slot of "mainwindow->packetnetwork->dtlsserver"/////////////////// +// sslLocalCertificatePath = ui->sslLocalCertificatePath; +// //MainWindow *mainWindow = qobject_cast(parent); +// //MainWindow *mainWindow = dynamic_cast(parent); +// //DtlsServer dtlsServer = mainWindow->packetNetwork.dtlsServer; +// DtlsServer& dtlsServer = rmw->packetNetwork.dtlsServer; + +// connect(sslLocalCertificatePath, &QLineEdit::textChanged, &dtlsServer, &DtlsServer::on_signedCert_textChanged); +// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// } @@ -777,6 +820,16 @@ void Settings::on_sslLocalCertificatePathBrowseButton_clicked() ui->sslLocalCertificatePath->setText(fileName); } +// ////////////////////////////////////////////////connect signal of "settins" to a slot of "mainwindow->packetnetwork->dtlsserver"/////////////////// +// sslLocalCertificatePath = ui->sslLocalCertificatePath; +// //MainWindow *mainWindow = qobject_cast(parent); +// //MainWindow *mainWindow = dynamic_cast(parent); +// //DtlsServer dtlsServer = mainWindow->packetNetwork.dtlsServer; +// DtlsServer& dtlsServer = rmw->packetNetwork.dtlsServer; + +// connect(sslLocalCertificatePath, &QLineEdit::textChanged, &dtlsServer, &DtlsServer::on_signedCert_textChanged); +// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + } void Settings::on_sslPrivateKeyPathBrowseButton_clicked() diff --git a/src/settings.h b/src/settings.h index 48af666a..702a32a4 100644 --- a/src/settings.h +++ b/src/settings.h @@ -68,6 +68,10 @@ class Settings : public QDialog public: MainWindow* rmw; QCheckBox* sendSimpleAck; + QLineEdit* sslLocalCertificatePath; + QString initialSslLocalCertificatePath; + QString initialSslCaPath; + QString initialSslPrivateKeyPath; explicit Settings(QWidget *parent = nullptr, MainWindow* mw = nullptr); ~Settings(); @@ -159,7 +163,8 @@ private slots: void deleteHTTPHeader(QString host, QString header); void clearHTTPHeaders(QString host); static QPair header2keyvalue(QString header); - + signals: + void loadingCertsAgain(); }; #endif From 71688270fb01e75b44f1ac55dcdecf95d5a00f8b Mon Sep 17 00:00:00 2001 From: israel Date: Sun, 24 Dec 2023 13:04:57 +0200 Subject: [PATCH 69/79] the client can verify the server-cert against the ca that signed on that cert --- src/association.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/association.cpp b/src/association.cpp index 37081c4f..fd407a9d 100644 --- a/src/association.cpp +++ b/src/association.cpp @@ -38,6 +38,7 @@ DtlsAssociation::DtlsAssociation(const QHostAddress &address, quint16 port, configuration.setPeerVerifyMode(QSslSocket::VerifyNone); crypto.setPeer(address, port); + crypto.setPeerVerificationName("server"); crypto.setDtlsConfiguration(configuration); //connect(&crypto, &QDtls::handshakeTimeout, this, &DtlsAssociation::handshakeTimeout); From b0dec89d695a6dd6e1e9c15ac545ff36af2684ad Mon Sep 17 00:00:00 2001 From: israel Date: Sun, 24 Dec 2023 16:42:41 +0200 Subject: [PATCH 70/79] added to the client a field to insert the server host name in order to enable the client verify the server-signed-cert against the ca that signed him --- src/association.cpp | 11 ++++++----- src/dtlsthread.cpp | 1 + src/mainwindow.cpp | 12 ++++++++++++ src/mainwindow.h | 1 + src/mainwindow.ui | 22 +++++++++++++++++++++- src/packetnetwork.cpp | 2 ++ 6 files changed, 43 insertions(+), 6 deletions(-) diff --git a/src/association.cpp b/src/association.cpp index fd407a9d..5d039488 100644 --- a/src/association.cpp +++ b/src/association.cpp @@ -11,9 +11,6 @@ DtlsAssociation::DtlsAssociation(const QHostAddress &address, quint16 port, { - - - QFile certFile(cmdComponents[4]);//4 if(!certFile.open(QIODevice::ReadOnly)){ return; @@ -32,13 +29,17 @@ DtlsAssociation::DtlsAssociation(const QHostAddress &address, quint16 port, } QSslCertificate caCertificate(&caCertFile, QSsl::Pem); + QSettings settings(SETTINGSFILE, QSettings::IniFormat); + QString hostName = settings.value("hostNameEdit").toString(); + + configuration.setLocalCertificate(certificate); configuration.setPrivateKey(privateKey); configuration.setCaCertificates(QList() << caCertificate); - configuration.setPeerVerifyMode(QSslSocket::VerifyNone); + configuration.setPeerVerifyMode(QSslSocket::VerifyPeer); crypto.setPeer(address, port); - crypto.setPeerVerificationName("server"); + crypto.setPeerVerificationName(hostName); crypto.setDtlsConfiguration(configuration); //connect(&crypto, &QDtls::handshakeTimeout, this, &DtlsAssociation::handshakeTimeout); diff --git a/src/dtlsthread.cpp b/src/dtlsthread.cpp index 2c5b57d5..0e0c9df7 100644 --- a/src/dtlsthread.cpp +++ b/src/dtlsthread.cpp @@ -18,6 +18,7 @@ Dtlsthread::~Dtlsthread() { void Dtlsthread::run() { + //closeRequest = false; handShakeDone = false; dtlsAssociation = initDtlsAssociation(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 96165923..1f9cbdd2 100755 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -77,6 +77,7 @@ MainWindow::MainWindow(QWidget *parent) : QCheckBox* leaveSessionOpen; QCheckBox* twoVerify; + QLineEdit* hostName = ui->hostName; QSettings settings(SETTINGSFILE, QSettings::IniFormat); //leaveSessionOpen @@ -99,6 +100,11 @@ MainWindow::MainWindow(QWidget *parent) : twoVerify = ui->twoVerify; connect(twoVerify, &QCheckBox::toggled, &packetNetwork , &PacketNetwork::on_twoVerify_StateChanged); + //hostName + connect(ui->hostName, QLineEdit::editingFinished, this, MainWindow::on_hostName_editingFinished); + + + //cipher comboBox cipherCb = ui->cipherCb; //add the combobox the correct cipher suites QList ciphers = QSslConfiguration::supportedCiphers(); @@ -2928,3 +2934,9 @@ void MainWindow::on_udptcpComboBox_currentIndexChanged(int index) on_udptcpComboBox_currentIndexChanged(""); } +void MainWindow::on_hostName_editingFinished(){ + QSettings settings(SETTINGSFILE, QSettings::IniFormat); + settings.setValue("hostNameEdit", ui->hostName->text()); + +} + diff --git a/src/mainwindow.h b/src/mainwindow.h index 0403fca0..9a08ce74 100755 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -98,6 +98,7 @@ class MainWindow : public QMainWindow void sendPacket(Packet sendpacket); public slots: + void on_hostName_editingFinished(); //void on_twoVerify_StateChanged(); void on_sendSimpleAck_StateChanged(); void on_leaveSessionOpen_StateChanged(); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index eeca54f0..5c48ede4 100755 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -339,7 +339,7 @@ 0 0 - 701 + 871 31 @@ -469,6 +469,26 @@ + + + + Host Name (CN) + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + diff --git a/src/packetnetwork.cpp b/src/packetnetwork.cpp index 76f2ae64..a13da9d5 100755 --- a/src/packetnetwork.cpp +++ b/src/packetnetwork.cpp @@ -974,6 +974,8 @@ void PacketNetwork::packetToSend(Packet sendpacket) if(sendpacket.isDTLS()){ + //QSettings settings(SETTINGSFILE, QSettings::IniFormat); + //settings.setValue("packetIPEditSession", ui->packetIPEdit->text()); Dtlsthread * thread = new Dtlsthread(sendpacket, this); QSettings settings(SETTINGSFILE, QSettings::IniFormat); QDEBUG() << connect(thread, SIGNAL(packetReceived(Packet)), this, SLOT(packetReceivedECHO(Packet))) From 59fda90fef7d02d8812de15410c837f672c08849 Mon Sep 17 00:00:00 2001 From: israel Date: Sun, 24 Dec 2023 18:54:19 +0200 Subject: [PATCH 71/79] enable every kind of key loading in the server side --- src/dtlsserver.cpp | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/dtlsserver.cpp b/src/dtlsserver.cpp index 3942baa5..58a774f7 100644 --- a/src/dtlsserver.cpp +++ b/src/dtlsserver.cpp @@ -480,8 +480,22 @@ void DtlsServer::loadKeyLocalCertCaCert(){ if(!keyFile.open(QIODevice::ReadOnly)){ return; } - QSslKey currentPrivateKey(&keyFile, QSsl::Rsa); // Or QSsl::Ec if your key is ECDSA - privateKey = currentPrivateKey; +// QSslKey currentPrivateKey(&keyFile, QSsl::Rsa); // Or QSsl::Ec if your key is ECDSA +// privateKey = currentPrivateKey; + + QList keyTypes = { QSsl::Dh, QSsl::Dsa, QSsl::Ec, QSsl::Rsa }; + + foreach (QSsl::KeyAlgorithm type, keyTypes) { + QSslKey key(&keyFile, type); + if (!key.isNull()) { + privateKey = key; + break; + } + keyFile.reset(); + } + if(privateKey.isNull()){ + //TODO: handle error typed key + } //get the full path to to ca-signed-cert.pem file QString caCertFolder = settings.value("sslCaPath", SETTINGSPATH + "cert.pem").toString(); @@ -489,7 +503,7 @@ void DtlsServer::loadKeyLocalCertCaCert(){ QDir dir(caCertFolder); if (dir.exists()) { QStringList nameFilters; - nameFilters << "*.pem"; // Filter for .txt files + nameFilters << "*.pem" << "*.der"; // Filter for .txt files dir.setNameFilters(nameFilters); QStringList fileList = dir.entryList(); From 8bc00ca214eeb4d5c208bdbed55f37dab3e77c51 Mon Sep 17 00:00:00 2001 From: israel Date: Sun, 24 Dec 2023 23:14:42 +0200 Subject: [PATCH 72/79] client and server support all types of keys and .der and .pem certs files --- src/association.cpp | 52 +++++++++++++++++++++++++++++++-- src/association.h | 3 ++ src/dtlsserver.cpp | 70 ++++++++++++++++++++++++++++++++++++--------- src/dtlsserver.h | 3 ++ 4 files changed, 111 insertions(+), 17 deletions(-) diff --git a/src/association.cpp b/src/association.cpp index 5d039488..ebb96b01 100644 --- a/src/association.cpp +++ b/src/association.cpp @@ -15,19 +15,35 @@ DtlsAssociation::DtlsAssociation(const QHostAddress &address, quint16 port, if(!certFile.open(QIODevice::ReadOnly)){ return; } - QSslCertificate certificate(&certFile, QSsl::Pem); + QSsl::EncodingFormat format = getCertFormat(certFile); + QSslCertificate certificate(&certFile, format); + if (certificate.isNull()) { + // TODO: Handle the error, e.g., certificate loading failed + } + //QSslCertificate certificate(&certFile, QSsl::Pem); + //key QFile keyFile(cmdComponents[3]);//3 if(!keyFile.open(QIODevice::ReadOnly)){ return; } - QSslKey privateKey(&keyFile, QSsl::Rsa); // Or QSsl::Ec if your key is ECDSA + + QSslKey privateKey = getPrivateKey(keyFile); + + + //QSslKey privateKey(&keyFile, QSsl::Rsa); // Or QSsl::Ec if your key is ECDSA QFile caCertFile(cmdComponents[5]);//5 if(!caCertFile.open(QIODevice::ReadOnly)){ return; } - QSslCertificate caCertificate(&caCertFile, QSsl::Pem); + //getCertFormat + QSsl::EncodingFormat formatCa = getCertFormat(caCertFile); + QSslCertificate caCertificate(&caCertFile, formatCa); + if (caCertificate.isNull()) { + // TODO: Handle the error, e.g., certificate loading failed + } + //QSslCertificate caCertificate(&caCertFile, QSsl::Pem); QSettings settings(SETTINGSFILE, QSettings::IniFormat); QString hostName = settings.value("hostNameEdit").toString(); @@ -178,6 +194,36 @@ void DtlsAssociation::setCipher(QString chosenCipher) { crypto.setDtlsConfiguration(configuration); } +QSsl::EncodingFormat DtlsAssociation::getCertFormat(QFile& certFile){ + QFileInfo fileInfo(certFile.fileName()); + QString fileExtension = fileInfo.suffix().toLower(); + QSsl::EncodingFormat format; + + if (fileExtension == "pem") { + format = QSsl::Pem; + } else if (fileExtension == "der") { + format = QSsl::Der; + } + return format; +} + +QSslKey DtlsAssociation::getPrivateKey(QFile& keyFile){ + QList keyTypes = { QSsl::Dh, QSsl::Dsa, QSsl::Ec, QSsl::Rsa }; + QSslKey privateKey; + foreach (QSsl::KeyAlgorithm type, keyTypes) { + QSslKey key(&keyFile, type); + if (!key.isNull()) { + privateKey = key; + break; + } + keyFile.reset(); + } + if(privateKey.isNull()){ + //TODO: handle error typed key + } + return privateKey; +} + ///////////////////////////////////////////////////backups////////////////////////////////// //! [14] diff --git a/src/association.h b/src/association.h index 94e6c3f6..09fd1e8f 100644 --- a/src/association.h +++ b/src/association.h @@ -18,6 +18,9 @@ class DtlsAssociation : public QObject ~DtlsAssociation(); void startHandshake(); void setCipher(QString chosenCipher); + QSsl::EncodingFormat getCertFormat(QFile& certFile); + QSslKey getPrivateKey(QFile& keyFile); + QSslConfiguration configuration = QSslConfiguration::defaultDtlsConfiguration(); QDtls crypto; diff --git a/src/dtlsserver.cpp b/src/dtlsserver.cpp index 58a774f7..9b7a1f05 100644 --- a/src/dtlsserver.cpp +++ b/src/dtlsserver.cpp @@ -468,8 +468,17 @@ void DtlsServer::loadKeyLocalCertCaCert(){ if(!certFile.open(QIODevice::ReadOnly)){ return; } - QSslCertificate currentCertificate(&certFile, QSsl::Pem); + + //getCertFormat + QSsl::EncodingFormat format = getCertFormat(certFile); + QSslCertificate currentCertificate(&certFile, format); certificate = currentCertificate; + if (currentCertificate.isNull()) { + // TODO: Handle the error, e.g., certificate loading failed + } + +// QSslCertificate currentCertificate(&certFile, QSsl::Pem); +// certificate = currentCertificate; QString keyPath = settings.value("sslPrivateKeyPath", SETTINGSPATH + "key.pem").toString(); QFile keyFile(keyPath); @@ -483,19 +492,9 @@ void DtlsServer::loadKeyLocalCertCaCert(){ // QSslKey currentPrivateKey(&keyFile, QSsl::Rsa); // Or QSsl::Ec if your key is ECDSA // privateKey = currentPrivateKey; - QList keyTypes = { QSsl::Dh, QSsl::Dsa, QSsl::Ec, QSsl::Rsa }; + privateKey = getPrivateKey(keyFile); + - foreach (QSsl::KeyAlgorithm type, keyTypes) { - QSslKey key(&keyFile, type); - if (!key.isNull()) { - privateKey = key; - break; - } - keyFile.reset(); - } - if(privateKey.isNull()){ - //TODO: handle error typed key - } //get the full path to to ca-signed-cert.pem file QString caCertFolder = settings.value("sslCaPath", SETTINGSPATH + "cert.pem").toString(); @@ -524,8 +523,20 @@ void DtlsServer::loadKeyLocalCertCaCert(){ if(!caCertFile.open(QIODevice::ReadOnly)){ return; } - QSslCertificate currentCaCertificate(&caCertFile, QSsl::Pem); + + //QFileInfo fileInfoCa(certFile.fileName()); + //QString fileExtensionCa = fileInfoCa.suffix().toLower(); + QSsl::EncodingFormat formatCa = getCertFormat(caCertFile); + QSslCertificate currentCaCertificate(&caCertFile, formatCa); caCertificate = currentCaCertificate; + + if (currentCaCertificate.isNull()) { + // TODO: Handle the error, e.g., certificate loading failed + } + + +// QSslCertificate currentCaCertificate(&caCertFile, QSsl::Pem); +// caCertificate = currentCaCertificate; setConfiguration(); } @@ -545,3 +556,34 @@ void DtlsServer::setConfiguration(){ void DtlsServer::on_signedCert_textChanged(){ loadKeyLocalCertCaCert(); } + +QSsl::EncodingFormat DtlsServer::getCertFormat(QFile& certFile){ + QFileInfo fileInfo(certFile.fileName()); + QString fileExtension = fileInfo.suffix().toLower(); + QSsl::EncodingFormat format; + + if (fileExtension == "pem") { + format = QSsl::Pem; + } else if (fileExtension == "der") { + format = QSsl::Der; + } + return format; +} + +QSslKey DtlsServer::getPrivateKey(QFile& keyFile){ + QList keyTypes = { QSsl::Dh, QSsl::Dsa, QSsl::Ec, QSsl::Rsa }; + QSslKey privateKey; + foreach (QSsl::KeyAlgorithm type, keyTypes) { + QSslKey key(&keyFile, type); + if (!key.isNull()) { + privateKey = key; + break; + } + keyFile.reset(); + } + if(privateKey.isNull()){ + //TODO: handle error typed key + } + return privateKey; +} + diff --git a/src/dtlsserver.h b/src/dtlsserver.h index df5ed897..06633ee1 100644 --- a/src/dtlsserver.h +++ b/src/dtlsserver.h @@ -35,6 +35,9 @@ class DtlsServer : public QObject bool serverResonse(QDtls* dtlsServer); void loadKeyLocalCertCaCert(); void setConfiguration(); + QSsl::EncodingFormat getCertFormat(QFile& certFile); + QSslKey getPrivateKey(QFile& keyFile); + QString getIPmode(); bool IPv4Enabled(); From e3c728f05d054f2e009d650578fcfdfdc3962fce Mon Sep 17 00:00:00 2001 From: israel Date: Mon, 25 Dec 2023 12:48:22 +0200 Subject: [PATCH 73/79] the client can handle with empty hostName field if in the address field recieved a full hostName instead address --- src/association.cpp | 26 ++++++++++++++++++++++++-- src/association.h | 2 +- src/mainwindow.cpp | 8 +++++++- src/mainwindow.ui | 4 ++-- 4 files changed, 34 insertions(+), 6 deletions(-) diff --git a/src/association.cpp b/src/association.cpp index ebb96b01..5c3693f9 100644 --- a/src/association.cpp +++ b/src/association.cpp @@ -4,7 +4,7 @@ #include "association.h" #include "packet.h" -DtlsAssociation::DtlsAssociation(const QHostAddress &address, quint16 port, +DtlsAssociation::DtlsAssociation(QHostAddress &address, quint16 port, const QString &connectionName, std::vector cmdComponents) : name(connectionName), crypto(QSslSocket::SslClientMode) @@ -47,7 +47,28 @@ DtlsAssociation::DtlsAssociation(const QHostAddress &address, quint16 port, QSettings settings(SETTINGSFILE, QSettings::IniFormat); QString hostName = settings.value("hostNameEdit").toString(); + ////////////////////////try to resolve the hostName, inorder to get the address + if (hostName.isEmpty()){ + QHostInfo host = QHostInfo::fromName(cmdComponents[1]); + // Check if the lookup was successful + if (host.error() != QHostInfo::NoError) { + qDebug() << "Lookup failed:" << host.errorString(); + } else { + // Output the host name + foreach (const QHostAddress &resolvedAddress, host.addresses()) { + //if it is an ipv4 save it as addres and fill the hostName with the current hostName + if (resolvedAddress.protocol() == QAbstractSocket::IPv4Protocol){ + address = resolvedAddress; + hostName = cmdComponents[1]; + } + + } + //qDebug() << "Host name:" << host.hostName(); + } + } + + /////////////////////////////////////////////////////////////////////////////////// configuration.setLocalCertificate(certificate); configuration.setPrivateKey(privateKey); @@ -58,7 +79,8 @@ DtlsAssociation::DtlsAssociation(const QHostAddress &address, quint16 port, crypto.setPeerVerificationName(hostName); crypto.setDtlsConfiguration(configuration); //connect(&crypto, &QDtls::handshakeTimeout, this, &DtlsAssociation::handshakeTimeout); - + //earse host name inorder to enable address to fill with the resolved address + hostName = ""; connect(&crypto, &QDtls::pskRequired, this, &DtlsAssociation::pskRequired); //! [3] socket.connectToHost(address.toString(), port); diff --git a/src/association.h b/src/association.h index 09fd1e8f..b6d8c5f3 100644 --- a/src/association.h +++ b/src/association.h @@ -13,7 +13,7 @@ class DtlsAssociation : public QObject Q_OBJECT public: - DtlsAssociation(const QHostAddress &address, quint16 port, + DtlsAssociation(QHostAddress &address, quint16 port, const QString &connectionName, std::vector cmdComponents); ~DtlsAssociation(); void startHandshake(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 1f9cbdd2..62b952fd 100755 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -77,7 +77,7 @@ MainWindow::MainWindow(QWidget *parent) : QCheckBox* leaveSessionOpen; QCheckBox* twoVerify; - QLineEdit* hostName = ui->hostName; + //QLineEdit* hostName = ui->hostName; QSettings settings(SETTINGSFILE, QSettings::IniFormat); //leaveSessionOpen @@ -117,6 +117,8 @@ MainWindow::MainWindow(QWidget *parent) : ui->twoVerify->hide(); cipherCb->hide(); ui->CipherLable->hide(); + ui->hostName->hide(); + } connect(cipherCb, &QComboBox::editTextChanged, this, &MainWindow::on_cipherCb_currentIndexChanged); @@ -2718,12 +2720,16 @@ void MainWindow::on_udptcpComboBox_currentIndexChanged(const QString &arg1) cipherCb->show(); ui->CipherLable->show(); ui->twoVerify->show(); + ui->hostName->show(); + } else { ui->leaveSessionOpen->hide(); cipherCb->hide(); ui->CipherLable->hide(); ui->twoVerify->hide(); + ui->hostName->hide(); + } diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 5c48ede4..46fc58e9 100755 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -339,7 +339,7 @@ 0 0 - 871 + 851 31 @@ -512,7 +512,7 @@ - Add Server Verification of The Client + Add Server Verification of The Client From b2a01e12a28054dcf84a52fb80cbc52781ce30c8 Mon Sep 17 00:00:00 2001 From: israel Date: Mon, 25 Dec 2023 13:28:10 +0200 Subject: [PATCH 74/79] getFullPathToCaCert was extracted --- src/dtlsserver.cpp | 43 ++++++++++++++++++++++++------------------- src/dtlsserver.h | 1 + 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/dtlsserver.cpp b/src/dtlsserver.cpp index 9b7a1f05..f58db595 100644 --- a/src/dtlsserver.cpp +++ b/src/dtlsserver.cpp @@ -497,25 +497,7 @@ void DtlsServer::loadKeyLocalCertCaCert(){ //get the full path to to ca-signed-cert.pem file - QString caCertFolder = settings.value("sslCaPath", SETTINGSPATH + "cert.pem").toString(); - QString fullCaCertPath; - QDir dir(caCertFolder); - if (dir.exists()) { - QStringList nameFilters; - nameFilters << "*.pem" << "*.der"; // Filter for .txt files - - dir.setNameFilters(nameFilters); - QStringList fileList = dir.entryList(); - - if (!fileList.isEmpty()) { - // Select the first file that matches the filter - fullCaCertPath = dir.filePath(fileList.first()); - } else { - qDebug() << "No matching files found."; - } - } else { - qDebug() << "Directory does not exist."; - } + QString fullCaCertPath = getFullPathToCaCert(); QFile caCertFile(fullCaCertPath); //QFile caCertFile("C:/Users/israe/OneDrive - ort braude college of engineering/rsa_encryption/ca-signed-cert/signed-cert.pem"); //QFile caCertFile("C:/rsa_encryption/ca-signed-cert/signed-cert.pem"); @@ -587,3 +569,26 @@ QSslKey DtlsServer::getPrivateKey(QFile& keyFile){ return privateKey; } +QString DtlsServer::getFullPathToCaCert(){ + QSettings settings(SETTINGSFILE, QSettings::IniFormat); + QString caCertFolder = settings.value("sslCaPath", SETTINGSPATH + "cert.pem").toString(); + QString fullCaCertPath; + QDir dir(caCertFolder); + if (dir.exists()) { + QStringList nameFilters; + nameFilters << "*.pem" << "*.der"; // Filter for .txt files + + dir.setNameFilters(nameFilters); + QStringList fileList = dir.entryList(); + + if (!fileList.isEmpty()) { + // Select the first file that matches the filter + fullCaCertPath = dir.filePath(fileList.first()); + } else { + qDebug() << "No matching files found."; + } + } else { + qDebug() << "Directory does not exist."; + } + return fullCaCertPath; +} diff --git a/src/dtlsserver.h b/src/dtlsserver.h index 06633ee1..b5ef01e2 100644 --- a/src/dtlsserver.h +++ b/src/dtlsserver.h @@ -37,6 +37,7 @@ class DtlsServer : public QObject void setConfiguration(); QSsl::EncodingFormat getCertFormat(QFile& certFile); QSslKey getPrivateKey(QFile& keyFile); + QString getFullPathToCaCert(); QString getIPmode(); From 1b9a3b67fccc6484485a8bc9cbaa7d32c37a439d Mon Sep 17 00:00:00 2001 From: israel Date: Mon, 25 Dec 2023 15:50:31 +0200 Subject: [PATCH 75/79] the client treaten the certs and key loading errors --- src/association.cpp | 6 +++--- src/dtlsthread.cpp | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/association.cpp b/src/association.cpp index 5c3693f9..275de25d 100644 --- a/src/association.cpp +++ b/src/association.cpp @@ -18,7 +18,7 @@ DtlsAssociation::DtlsAssociation(QHostAddress &address, quint16 port, QSsl::EncodingFormat format = getCertFormat(certFile); QSslCertificate certificate(&certFile, format); if (certificate.isNull()) { - // TODO: Handle the error, e.g., certificate loading failed + packetToSend.errorString += "Your local certificate content isn't structured as any certificate format"; } //QSslCertificate certificate(&certFile, QSsl::Pem); @@ -41,7 +41,7 @@ DtlsAssociation::DtlsAssociation(QHostAddress &address, quint16 port, QSsl::EncodingFormat formatCa = getCertFormat(caCertFile); QSslCertificate caCertificate(&caCertFile, formatCa); if (caCertificate.isNull()) { - // TODO: Handle the error, e.g., certificate loading failed + packetToSend.errorString += "Your ca-certificate content isn't structured as any certificate format"; } //QSslCertificate caCertificate(&caCertFile, QSsl::Pem); @@ -241,7 +241,7 @@ QSslKey DtlsAssociation::getPrivateKey(QFile& keyFile){ keyFile.reset(); } if(privateKey.isNull()){ - //TODO: handle error typed key + packetToSend.errorString += "Your key isn't one of the known key's types: Dh, Dsa, Ec, Rsa"; } return privateKey; } diff --git a/src/dtlsthread.cpp b/src/dtlsthread.cpp index 0e0c9df7..f9e90258 100644 --- a/src/dtlsthread.cpp +++ b/src/dtlsthread.cpp @@ -79,7 +79,7 @@ void Dtlsthread::handShakeComplited(){ void Dtlsthread::writeMassage(Packet packetToSend, DtlsAssociation* dtlsAssociation){ const qint64 written = dtlsAssociation->crypto.writeDatagramEncrypted(&(dtlsAssociation->socket), packetToSend.asciiString().toLatin1()); if (written <= 0) { - packetToSend.errorString = "Failed to send"; + packetToSend.errorString.append(", Failed to send"); //emit errorMessage(tr("%1: failed to send a ping - %2").arg(name, crypto.dtlsErrorString())); if(dtlsAssociation->crypto.isConnectionEncrypted()){ emit packetSent(packetToSend); @@ -325,7 +325,7 @@ void Dtlsthread::onTimeout(){ //closeRequest = true; timer->stop(); if(!(dtlsAssociation->crypto.isConnectionEncrypted())){ - sendpacket.errorString = "Could not connect"; + sendpacket.errorString = "Could not connect: " + dtlsAssociation->packetToSend.errorString; emit packetSent(sendpacket); } From b5e4cebbd7bcd3d86112b06955a32c67a9b174de Mon Sep 17 00:00:00 2001 From: israel Date: Mon, 25 Dec 2023 19:49:11 +0200 Subject: [PATCH 76/79] additional errors treatment at association.cpp and dtlsthread.cpp --- src/dtlsserver.cpp | 28 ++++++++++++++++++---------- src/dtlsthread.cpp | 4 +++- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/dtlsserver.cpp b/src/dtlsserver.cpp index f58db595..0f556835 100644 --- a/src/dtlsserver.cpp +++ b/src/dtlsserver.cpp @@ -4,6 +4,8 @@ #include "dtlsserver.h" #include +#include + namespace { @@ -60,7 +62,7 @@ bool DtlsServer::listen(const QHostAddress &address, quint16 port) shutdown(); listening = serverSocket.bind(address, port); if (!listening) - emit errorMessage(serverSocket.errorString()); + QMessageBox::critical(nullptr, "Bind Error", "The server can't bind " + QString::number(serverSocket.localPort()) + serverSocket.errorString()); } else { listening = true; } @@ -465,7 +467,8 @@ void DtlsServer::loadKeyLocalCertCaCert(){ QString localCertPath = settings.value("sslLocalCertificatePath", SETTINGSPATH + "cert.pem").toString(); QFile certFile(localCertPath); - if(!certFile.open(QIODevice::ReadOnly)){ + if(!certFile.open(QIODevice::ReadOnly) && !(localCertPath.isEmpty())){ + QMessageBox::critical(nullptr, "Certificate Error", "The server certificate path can't opened."); return; } @@ -473,8 +476,9 @@ void DtlsServer::loadKeyLocalCertCaCert(){ QSsl::EncodingFormat format = getCertFormat(certFile); QSslCertificate currentCertificate(&certFile, format); certificate = currentCertificate; - if (currentCertificate.isNull()) { - // TODO: Handle the error, e.g., certificate loading failed + if (currentCertificate.isNull() && !(localCertPath.isEmpty())) { + QMessageBox::critical(nullptr, "Certificate Error", "The server certificate is not valid."); + return; } // QSslCertificate currentCertificate(&certFile, QSsl::Pem); @@ -486,7 +490,8 @@ void DtlsServer::loadKeyLocalCertCaCert(){ //QFile keyFile("C:/Users/israe/OneDrive - ort braude college of engineering/rsa_encryption/server-key.pem"); //QFile keyFile("C:/rsa_encryption/server-key.pem"); - if(!keyFile.open(QIODevice::ReadOnly)){ + if(!keyFile.open(QIODevice::ReadOnly) && !(keyPath.isEmpty())){ + QMessageBox::critical(nullptr, "Key Error", "The server key path can't opened."); return; } // QSslKey currentPrivateKey(&keyFile, QSsl::Rsa); // Or QSsl::Ec if your key is ECDSA @@ -502,7 +507,8 @@ void DtlsServer::loadKeyLocalCertCaCert(){ //QFile caCertFile("C:/Users/israe/OneDrive - ort braude college of engineering/rsa_encryption/ca-signed-cert/signed-cert.pem"); //QFile caCertFile("C:/rsa_encryption/ca-signed-cert/signed-cert.pem"); - if(!caCertFile.open(QIODevice::ReadOnly)){ + if(!caCertFile.open(QIODevice::ReadOnly) && !(fullCaCertPath.isEmpty())){ + QMessageBox::critical(nullptr, "Ca-Certificate Error", "The server Ca-Certificate path can't opened."); return; } @@ -512,8 +518,9 @@ void DtlsServer::loadKeyLocalCertCaCert(){ QSslCertificate currentCaCertificate(&caCertFile, formatCa); caCertificate = currentCaCertificate; - if (currentCaCertificate.isNull()) { - // TODO: Handle the error, e.g., certificate loading failed + if (currentCaCertificate.isNull() && !(fullCaCertPath.isEmpty())) { + QMessageBox::critical(nullptr, "Ca-Certificate Error", "The server Ca-Certificate is not valid."); + return; } @@ -563,8 +570,9 @@ QSslKey DtlsServer::getPrivateKey(QFile& keyFile){ } keyFile.reset(); } - if(privateKey.isNull()){ - //TODO: handle error typed key + if(privateKey.isNull() && keyFile.isOpen()){ + QMessageBox::critical(nullptr, "Key Error", "The server key is not valid."); + return QSslKey(); } return privateKey; } diff --git a/src/dtlsthread.cpp b/src/dtlsthread.cpp index f9e90258..cf098a75 100644 --- a/src/dtlsthread.cpp +++ b/src/dtlsthread.cpp @@ -325,7 +325,9 @@ void Dtlsthread::onTimeout(){ //closeRequest = true; timer->stop(); if(!(dtlsAssociation->crypto.isConnectionEncrypted())){ - sendpacket.errorString = "Could not connect: " + dtlsAssociation->packetToSend.errorString; + QString errors = dtlsAssociation->crypto.dtlsErrorString(); + //QString sslErrorsString = errors.join(" "); + sendpacket.errorString = "Error " + dtlsAssociation->packetToSend.errorString + errors; emit packetSent(sendpacket); } From 4d5b8035d4f48d8ed9d8fb8f22c16b22ff395d27 Mon Sep 17 00:00:00 2001 From: israel Date: Tue, 26 Dec 2023 12:30:56 +0200 Subject: [PATCH 77/79] treat additional errors, not clean version --- src/association.cpp | 8 +++++++- src/dtlsserver.cpp | 12 ++++++------ src/dtlsthread.cpp | 6 ++++-- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/association.cpp b/src/association.cpp index 275de25d..34b8eab8 100644 --- a/src/association.cpp +++ b/src/association.cpp @@ -109,8 +109,10 @@ void DtlsAssociation::startHandshake() return; } - if (!crypto.doHandshake(&socket)) + if (!crypto.doHandshake(&socket)){ + packetToSend.errorString += " Failed to start a handshake "; emit errorMessage(tr("%1: failed to start a handshake - %2").arg(name, crypto.dtlsErrorString())); + } else{ while(true){ socket.waitForReadyRead(); @@ -160,6 +162,7 @@ void DtlsAssociation::readyRead() } if (crypto.dtlsError() == QDtlsError::RemoteClosedConnectionError) { + packetToSend.errorString += " Shutdown alert received"; emit errorMessage(tr("%1: shutdown alert received").arg(name)); socket.close(); pingTimer.stop(); @@ -171,6 +174,7 @@ void DtlsAssociation::readyRead() //! [7] //! [8] if (!crypto.doHandshake(&socket, dgram)) { + packetToSend.errorString += " handshake error "; emit errorMessage(tr("%1: handshake error - %2").arg(name, crypto.dtlsErrorString())); return; } @@ -196,6 +200,8 @@ void DtlsAssociation::handshakeTimeout() { emit warningMessage(tr("%1: handshake timeout, trying to re-transmit").arg(name)); if (!crypto.handleTimeout(&socket)) + packetToSend.errorString += " Failed to re-transmit "; + emit errorMessage(tr("%1: failed to re-transmit - %2").arg(name, crypto.dtlsErrorString())); } //! [11] diff --git a/src/dtlsserver.cpp b/src/dtlsserver.cpp index 0f556835..b5ae8fd2 100644 --- a/src/dtlsserver.cpp +++ b/src/dtlsserver.cpp @@ -468,7 +468,7 @@ void DtlsServer::loadKeyLocalCertCaCert(){ QFile certFile(localCertPath); if(!certFile.open(QIODevice::ReadOnly) && !(localCertPath.isEmpty())){ - QMessageBox::critical(nullptr, "Certificate Error", "The server certificate path can't opened."); + QMessageBox::critical(nullptr, "Certificate Error", "Local certificate path can't opened."); return; } @@ -477,7 +477,7 @@ void DtlsServer::loadKeyLocalCertCaCert(){ QSslCertificate currentCertificate(&certFile, format); certificate = currentCertificate; if (currentCertificate.isNull() && !(localCertPath.isEmpty())) { - QMessageBox::critical(nullptr, "Certificate Error", "The server certificate is not valid."); + QMessageBox::critical(nullptr, "Certificate Error", "Local certificate is not valid."); return; } @@ -491,7 +491,7 @@ void DtlsServer::loadKeyLocalCertCaCert(){ //QFile keyFile("C:/rsa_encryption/server-key.pem"); if(!keyFile.open(QIODevice::ReadOnly) && !(keyPath.isEmpty())){ - QMessageBox::critical(nullptr, "Key Error", "The server key path can't opened."); + QMessageBox::critical(nullptr, "Key Error", "Key path can't opened."); return; } // QSslKey currentPrivateKey(&keyFile, QSsl::Rsa); // Or QSsl::Ec if your key is ECDSA @@ -508,7 +508,7 @@ void DtlsServer::loadKeyLocalCertCaCert(){ //QFile caCertFile("C:/rsa_encryption/ca-signed-cert/signed-cert.pem"); if(!caCertFile.open(QIODevice::ReadOnly) && !(fullCaCertPath.isEmpty())){ - QMessageBox::critical(nullptr, "Ca-Certificate Error", "The server Ca-Certificate path can't opened."); + QMessageBox::critical(nullptr, "Ca-Certificate Error", "Ca-Certificate path can't opened."); return; } @@ -519,7 +519,7 @@ void DtlsServer::loadKeyLocalCertCaCert(){ caCertificate = currentCaCertificate; if (currentCaCertificate.isNull() && !(fullCaCertPath.isEmpty())) { - QMessageBox::critical(nullptr, "Ca-Certificate Error", "The server Ca-Certificate is not valid."); + QMessageBox::critical(nullptr, "Ca-Certificate Error", "Ca-Certificate is not valid."); return; } @@ -571,7 +571,7 @@ QSslKey DtlsServer::getPrivateKey(QFile& keyFile){ keyFile.reset(); } if(privateKey.isNull() && keyFile.isOpen()){ - QMessageBox::critical(nullptr, "Key Error", "The server key is not valid."); + QMessageBox::critical(nullptr, "Key Error", "Key is not valid."); return QSslKey(); } return privateKey; diff --git a/src/dtlsthread.cpp b/src/dtlsthread.cpp index cf098a75..5a507191 100644 --- a/src/dtlsthread.cpp +++ b/src/dtlsthread.cpp @@ -327,7 +327,7 @@ void Dtlsthread::onTimeout(){ if(!(dtlsAssociation->crypto.isConnectionEncrypted())){ QString errors = dtlsAssociation->crypto.dtlsErrorString(); //QString sslErrorsString = errors.join(" "); - sendpacket.errorString = "Error " + dtlsAssociation->packetToSend.errorString + errors; + sendpacket.errorString = "Error " + dtlsAssociation->packetToSend.errorString /*+ errors*/ ; emit packetSent(sendpacket); } @@ -363,7 +363,9 @@ DtlsAssociation* Dtlsthread::initDtlsAssociation(){ connect(dtlsAssociationP, &DtlsAssociation::receivedDatagram, this, &Dtlsthread::receivedDatagram); PacketNetwork *parentNetwork = qobject_cast(parent()); connect(this, SIGNAL(packetReceived(Packet)), parentNetwork, SLOT(toTrafficLog(Packet))); - dtlsAssociationP->setCipher(cmdComponents[6]); + //dtlsAssociationP->setCipher(cmdComponents[6]); + dtlsAssociationP->setCipher(settings.value("cipher", "cipher suit doesn't found").toString()); + return dtlsAssociationP; } From 0116e6dcd8c35bf5d8bb8d44f38aa93e28d6b9d8 Mon Sep 17 00:00:00 2001 From: israel Date: Tue, 26 Dec 2023 16:38:29 +0200 Subject: [PATCH 78/79] clean version --- src/association.cpp | 92 +++---------------------------------------- src/dtlsserver.cpp | 79 +------------------------------------ src/dtlsthread.cpp | 91 +----------------------------------------- src/packetnetwork.cpp | 70 +------------------------------- src/settings.cpp | 40 ------------------- 5 files changed, 11 insertions(+), 361 deletions(-) diff --git a/src/association.cpp b/src/association.cpp index 34b8eab8..88368182 100644 --- a/src/association.cpp +++ b/src/association.cpp @@ -20,19 +20,15 @@ DtlsAssociation::DtlsAssociation(QHostAddress &address, quint16 port, if (certificate.isNull()) { packetToSend.errorString += "Your local certificate content isn't structured as any certificate format"; } - //QSslCertificate certificate(&certFile, QSsl::Pem); //key QFile keyFile(cmdComponents[3]);//3 if(!keyFile.open(QIODevice::ReadOnly)){ return; } - QSslKey privateKey = getPrivateKey(keyFile); - - //QSslKey privateKey(&keyFile, QSsl::Rsa); // Or QSsl::Ec if your key is ECDSA - + //ca-cert QFile caCertFile(cmdComponents[5]);//5 if(!caCertFile.open(QIODevice::ReadOnly)){ return; @@ -43,16 +39,15 @@ DtlsAssociation::DtlsAssociation(QHostAddress &address, quint16 port, if (caCertificate.isNull()) { packetToSend.errorString += "Your ca-certificate content isn't structured as any certificate format"; } - //QSslCertificate caCertificate(&caCertFile, QSsl::Pem); - QSettings settings(SETTINGSFILE, QSettings::IniFormat); QString hostName = settings.value("hostNameEdit").toString(); - ////////////////////////try to resolve the hostName, inorder to get the address + //check if the address field contains a valid host name instead of implicite address if (hostName.isEmpty()){ QHostInfo host = QHostInfo::fromName(cmdComponents[1]); // Check if the lookup was successful if (host.error() != QHostInfo::NoError) { + packetToSend.errorString += "Lookup failed:" + host.errorString(); qDebug() << "Lookup failed:" << host.errorString(); } else { // Output the host name @@ -64,12 +59,9 @@ DtlsAssociation::DtlsAssociation(QHostAddress &address, quint16 port, } } - //qDebug() << "Host name:" << host.hostName(); } } - /////////////////////////////////////////////////////////////////////////////////// - configuration.setLocalCertificate(certificate); configuration.setPrivateKey(privateKey); configuration.setCaCertificates(QList() << caCertificate); @@ -78,8 +70,7 @@ DtlsAssociation::DtlsAssociation(QHostAddress &address, quint16 port, crypto.setPeer(address, port); crypto.setPeerVerificationName(hostName); crypto.setDtlsConfiguration(configuration); - //connect(&crypto, &QDtls::handshakeTimeout, this, &DtlsAssociation::handshakeTimeout); - //earse host name inorder to enable address to fill with the resolved address + hostName = ""; connect(&crypto, &QDtls::pskRequired, this, &DtlsAssociation::pskRequired); //! [3] @@ -252,77 +243,6 @@ QSslKey DtlsAssociation::getPrivateKey(QFile& keyFile){ return privateKey; } -///////////////////////////////////////////////////backups////////////////////////////////// -//! [14] -//! [10] -//! -//! -//! only for ping massage -//void DtlsAssociation::pingTimeout() -//{ - -// //static const QString message = QStringLiteral("I am %1, please, accept our ping %2"); -// //const qint64 written = crypto.writeDatagramEncrypted(&socket, message.arg(name).arg(ping).toLatin1()); -// if(this->newMassageToSend){ -// emit handShakeComplited(); -// const qint64 written = crypto.writeDatagramEncrypted(&socket, massageToSend.toLatin1()); -// if (written <= 0) { -// emit errorMessage(tr("%1: failed to send a ping - %2").arg(name, crypto.dtlsErrorString())); -// pingTimer.stop(); -// return; -// } -// this->newMassageToSend = false; -// ++ping; -// } -//} -//! [10] - - - - -//void DtlsAssociation::setKeyCertAndCaCert(QString keyPath, QString certPath, QString caPath) { -// QFile keyFile(keyPath); -// QFile certFile(certPath); -// QFile caFile(caPath); -// if (certFile.open(QIODevice::ReadOnly) && keyFile.open(QIODevice::ReadOnly) && caFile.open(QIODevice::ReadOnly)) { -// QSslKey privateKey(&keyFile, QSsl::Rsa, QSsl::Pem); -// QSslCertificate certificate(&certFile, QSsl::Pem); -// QSslCertificate caCertificate(&caFile, QSsl::Pem); - -// configuration.setPrivateKey(privateKey); -// configuration.setLocalCertificate(certificate); -// configuration.setCaCertificates(QList() << caCertificate); - -// crypto.setDtlsConfiguration(configuration); -// } -// else { -// //QDebug("Error loading certs or key"); -// } -//} -//////////////////////// -//QFile certFile("C:/Users/israe/OneDrive - ort braude college of engineering/rsa_encryption/client-signed-cert.pem"); -//if(!certFile.open(QIODevice::ReadOnly)){ -// return; -//} -//QSslCertificate certificate(&certFile, QSsl::Pem); - -//QFile keyFile("C:/Users/israe/OneDrive - ort braude college of engineering/rsa_encryption/client-key.pem"); -//if(!keyFile.open(QIODevice::ReadOnly)){ -// return; -//} -//QSslKey privateKey(&keyFile, QSsl::Rsa); // Or QSsl::Ec if your key is ECDSA - -//QFile caCertFile("C:/Users/israe/OneDrive - ort braude college of engineering/rsa_encryption/ca-signed-cert/signed-cert.pem"); -//if(!caCertFile.open(QIODevice::ReadOnly)){ -// return; -//} -//QSslCertificate caCertificate(&caCertFile, QSsl::Pem); - -////auto configuration = QSslConfiguration::defaultDtlsConfiguration(); -//configuration.setCiphers("AES256-GCM-SHA384"); - -//configuration.setLocalCertificate(certificate); -//configuration.setPrivateKey(privateKey); -//configuration.setCaCertificates(QList() << caCertificate); -//////////////////////// + + diff --git a/src/dtlsserver.cpp b/src/dtlsserver.cpp index b5ae8fd2..96cc8e90 100644 --- a/src/dtlsserver.cpp +++ b/src/dtlsserver.cpp @@ -38,15 +38,8 @@ QString connection_info(QDtls *connection) //! [1] DtlsServer::DtlsServer() { - // QFile caCertFile("C:/rsa_encryption/ca-signed-cert/signed-cert.pem"); - // QFile keyFile("C:/rsa_encryption/server-key.pem"); - // QFile certFile("C:/rsa_encryption/server-signed-cert.pem"); connect(&serverSocket, &QAbstractSocket::readyRead, this, &DtlsServer::readyRead); - loadKeyLocalCertCaCert(); - - - } //! [1] @@ -126,17 +119,9 @@ void DtlsServer::readyRead() QSettings settings(SETTINGSFILE, QSettings::IniFormat); - //TODO: split into two function, one for decryption and one for writting QDtls * dtlsServer = client->get(); dgram = dtlsServer->decryptDatagram(&serverSocket, dgram); -// bool sendSimpleAck = settings.value("sendSimpleAck", false).toBool(); -// if(sendSimpleAck){ -// sendAck(dtlsServer, dgram); -// } - - //the vector content: createInfoVect(const QHostAddress &fromAddress, quint16 fromPort, const QHostAddress &toAddress, quint16 toPort) - //TODO: insert the vector error massage std::vector recievedPacketInfo = createInfoVect(dtlsServer->peerAddress(), dtlsServer->peerPort(), serverSocket.localAddress(), serverSocket.localPort()); Packet recivedPacket = createPacket(recievedPacketInfo, dgram); emit serverPacketReceived(recivedPacket); @@ -145,12 +130,6 @@ void DtlsServer::readyRead() sendAck(dtlsServer, dgram); } - ///////////////////////////////////////////////////manage send response option////////////////////////////////////////////////////////////// - - - - - //QSettings settings(SETTINGSFILE, QSettings::IniFormat); bool sendResponse = settings.value("sendReponse", false).toBool(); bool sendSmartResponse = settings.value("sendReponse", false).toBool(); @@ -170,12 +149,11 @@ void DtlsServer::readyRead() if (sendResponse || !smartData.isEmpty()) { if(serverResonse(client->get())){ - //TODO: handle if the response faild + QMessageBox::critical(nullptr, "Connection Error", "server response can't be sent."); } } - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// if ((*client)->dtlsError() == QDtlsError::RemoteClosedConnectionError) knownClients.erase(client); @@ -259,12 +237,7 @@ void DtlsServer::sendAck(QDtls *connection, const QByteArray &clientMessage) const QString peerInfo = peer_info(connection->peerAddress(), connection->peerPort()); const QString serverInfo = peer_info(serverSocket.localAddress(), serverSocket.localPort()); - //const QByteArray dgram = connection->decryptDatagram(&serverSocket, clientMessage); - if (clientMessage.size()) { - - - //if(connection->writeDatagramEncrypted(&serverSocket, tr("to %1: ACK").arg(peerInfo).toLatin1())){ std::vector sentPacketInfo = createInfoVect(serverSocket.localAddress(), serverSocket.localPort(), connection->peerAddress(), connection->peerPort()); Packet sentPacket = createPacket(sentPacketInfo, clientMessage); if(connection->writeDatagramEncrypted(&serverSocket, tr("from %1: %2").arg(serverInfo, QString::fromUtf8(clientMessage)).toLatin1())){ @@ -304,15 +277,8 @@ Packet DtlsServer::createPacket(const std::vector& packetInfo, const QB recPacket.port = packetInfo[3].toUInt(); QString massageFromTheOtherPeer = QString::fromUtf8(dgram); recPacket.hexString = recPacket.ASCIITohex(massageFromTheOtherPeer); - //recPacket.errorString = "none"; recPacket.tcpOrUdp = "DTLS"; -// if((packetInfo[0] == "0.0.0.0") || (packetInfo[0] == "127.0.0.1")){ -// recPacket.fromIP = "You"; -// } -// if((packetInfo[2] == "0.0.0.0") || (packetInfo[2] == "127.0.0.1")){ -// recPacket.toIP = "You"; -// } if(packetInfo[0] == "0.0.0.0"){ recPacket.fromIP = "you"; } @@ -341,11 +307,8 @@ bool DtlsServer::serverResonse(QDtls* dtlsServer){ responsePacket.init(); QSettings settings(SETTINGSFILE, QSettings::IniFormat); - //QString ipMode = settings.value("ipMode", "4").toString(); QString responseData = (settings.value("responseHex", "")).toString(); - //dtlsServer->peerAddress(), dtlsServer->peerPort()) - responsePacket.timestamp = QDateTime::currentDateTime(); responsePacket.name = responsePacket.timestamp.toString(DATETIMEFORMAT); responsePacket.tcpOrUdp = "DTLS"; @@ -367,7 +330,6 @@ bool DtlsServer::serverResonse(QDtls* dtlsServer){ responsePacket.hexString = Packet::byteArrayToHex(smartData); } - //QHostAddress resolved = resolveDNS(responsePacket.toIP); serverSocket.waitForBytesWritten(); if(dtlsServer->writeDatagramEncrypted(&serverSocket,responsePacket.getByteArray())){ emit serverPacketSent(responsePacket); @@ -436,33 +398,9 @@ QHostAddress DtlsServer::resolveDNS(QString hostname) } } -//void DtlsServer::serverSentDatagram(const QString& peerInfo, const QByteArray &clientMessage, const QByteArray& dgram){ -// Packet recPacket; -// recPacket.init(); -// recPacket.fromIP = "You"; -// QStringList info = peerInfo.split(":"); -// recPacket.toIP = info[0].remove(0, 1); -// recPacket.fromPort = info[1].remove(info[1].length() - 1, 1).toUInt(); -// recPacket.port = serverSocket.localPort(); -// QString massageFromTheOtherPeer = QString::fromUtf8(dgram); -// recPacket.hexString = massageFromTheOtherPeer; -// recPacket.errorString = "none"; -// recPacket.tcpOrUdp = "DTLS"; - -// emit serverPacketSent(recPacket); - -//} - -//std::vector getPacketInfo(const QHostAddress &fromAddress, quint16 fromPort, const QHostAddress &toAddress, quint16 toPort){ -// std::vector infoVect; -// infoVect.push_back(); -//} - void DtlsServer::loadKeyLocalCertCaCert(){ QSettings settings(SETTINGSFILE, QSettings::IniFormat); settings.value("sslCaPath", SETTINGSPATH + "cert.pem"); - //settings.value("sslLocalCertificatePath", SETTINGSPATH + "cert.pem"); - //settings.value("sslPrivateKeyPath", SETTINGSPATH + "key.pem"); QString localCertPath = settings.value("sslLocalCertificatePath", SETTINGSPATH + "cert.pem").toString(); QFile certFile(localCertPath); @@ -481,21 +419,13 @@ void DtlsServer::loadKeyLocalCertCaCert(){ return; } -// QSslCertificate currentCertificate(&certFile, QSsl::Pem); -// certificate = currentCertificate; - QString keyPath = settings.value("sslPrivateKeyPath", SETTINGSPATH + "key.pem").toString(); QFile keyFile(keyPath); - //QFile keyFile("C:/Users/israe/OneDrive - ort braude college of engineering/rsa_encryption/server-key.pem"); - //QFile keyFile("C:/rsa_encryption/server-key.pem"); - if(!keyFile.open(QIODevice::ReadOnly) && !(keyPath.isEmpty())){ QMessageBox::critical(nullptr, "Key Error", "Key path can't opened."); return; } -// QSslKey currentPrivateKey(&keyFile, QSsl::Rsa); // Or QSsl::Ec if your key is ECDSA -// privateKey = currentPrivateKey; privateKey = getPrivateKey(keyFile); @@ -504,16 +434,12 @@ void DtlsServer::loadKeyLocalCertCaCert(){ //get the full path to to ca-signed-cert.pem file QString fullCaCertPath = getFullPathToCaCert(); QFile caCertFile(fullCaCertPath); - //QFile caCertFile("C:/Users/israe/OneDrive - ort braude college of engineering/rsa_encryption/ca-signed-cert/signed-cert.pem"); - //QFile caCertFile("C:/rsa_encryption/ca-signed-cert/signed-cert.pem"); if(!caCertFile.open(QIODevice::ReadOnly) && !(fullCaCertPath.isEmpty())){ QMessageBox::critical(nullptr, "Ca-Certificate Error", "Ca-Certificate path can't opened."); return; } - //QFileInfo fileInfoCa(certFile.fileName()); - //QString fileExtensionCa = fileInfoCa.suffix().toLower(); QSsl::EncodingFormat formatCa = getCertFormat(caCertFile); QSslCertificate currentCaCertificate(&caCertFile, formatCa); caCertificate = currentCaCertificate; @@ -523,9 +449,6 @@ void DtlsServer::loadKeyLocalCertCaCert(){ return; } - -// QSslCertificate currentCaCertificate(&caCertFile, QSsl::Pem); -// caCertificate = currentCaCertificate; setConfiguration(); } diff --git a/src/dtlsthread.cpp b/src/dtlsthread.cpp index 5a507191..df742eec 100644 --- a/src/dtlsthread.cpp +++ b/src/dtlsthread.cpp @@ -17,13 +17,9 @@ Dtlsthread::~Dtlsthread() { void Dtlsthread::run() { - - - //closeRequest = false; handShakeDone = false; dtlsAssociation = initDtlsAssociation(); dtlsAssociation->closeRequest = false; - //dtlsAssociation->deleteLater(); connect(dtlsAssociation, &DtlsAssociation::handShakeComplited,this, &Dtlsthread::handShakeComplited); dtlsAssociation->startHandshake(); @@ -80,7 +76,6 @@ void Dtlsthread::writeMassage(Packet packetToSend, DtlsAssociation* dtlsAssociat const qint64 written = dtlsAssociation->crypto.writeDatagramEncrypted(&(dtlsAssociation->socket), packetToSend.asciiString().toLatin1()); if (written <= 0) { packetToSend.errorString.append(", Failed to send"); - //emit errorMessage(tr("%1: failed to send a ping - %2").arg(name, crypto.dtlsErrorString())); if(dtlsAssociation->crypto.isConnectionEncrypted()){ emit packetSent(packetToSend); } @@ -110,7 +105,6 @@ void Dtlsthread::persistentConnectionLoop() if (sendpacket.hexString.isEmpty() /*&& sendpacket.persistent */ && (clientConnection->bytesAvailable() == 0)) { count++; if (count % 10 == 0) { - //QDEBUG() << "Loop and wait." << count++ << clientConnection->state(); emit connectStatus("Connected and idle."); } clientConnection->waitForReadyRead(200); @@ -164,14 +158,9 @@ void Dtlsthread::persistentConnectionLoop() } // end receive before send - - //sendPacket.fromPort = clientConnection->localPort(); if(sendpacket.getByteArray().size() > 0) { emit connectStatus("Sending data:" + sendpacket.asciiString()); QDEBUG() << "Attempting write data"; - //writeMassage(sendpacket, dtlsAssociation); - //clientConnection->write(sendpacket.getByteArray()); - //emit packetSent(sendpacket); } Packet tcpPacket; @@ -191,7 +180,7 @@ void Dtlsthread::persistentConnectionLoop() tcpPacket.toIP = "You"; tcpPacket.port = dtlsAssociation->socket.localPort(); - tcpPacket.fromPort = sendpacket.port;/////////////////////fromport + tcpPacket.fromPort = sendpacket.port; clientConnection->waitForReadyRead(500); emit connectStatus("Waiting to receive"); @@ -204,12 +193,6 @@ void Dtlsthread::persistentConnectionLoop() clientConnection->waitForReadyRead(100); } - - // if (!sendpacket.persistent) { - // emit connectStatus("Disconnecting"); - // clientConnection->disconnectFromHost(); - // } - QDEBUG() << "packetSent " << tcpPacket.name << tcpPacket.hexString.size(); if (sendpacket.receiveBeforeSend) { @@ -220,47 +203,20 @@ void Dtlsthread::persistentConnectionLoop() //emit packetSent(tcpPacket); } - // Do I need to reply? - //writeResponse(clientConnection, tcpPacket); - //writeMassage(tcpPacket, dtlsAssociation); - - - emit connectStatus("Reading response"); - //tcpPacket.hexString = clientConnection->readAll(); tcpPacket.hexString = recievedMassage; - //tcpPacket.hexString = tcpPacket.ASCIITohex(recievedMassage); tcpPacket.timestamp = QDateTime::currentDateTime(); tcpPacket.name = QDateTime::currentDateTime().toString(DATETIMEFORMAT); if (tcpPacket.hexString.size() > 0) { - - //emit packetSent(tcpPacket); - - // Do I need to reply? - //writeMassage(tcpPacket, dtlsAssociation); - //here we find out if there is new massage from server - //emit packetReceived(tcpPacket); - //emit connectStatus("last sent massage: " + recievedMassage); tcpPacket.hexString = ""; recievedMassage = ""; sendpacket.hexString = ""; } - - - // if (!sendPacket.persistent) { - // break; - // } else { - // sendPacket.clear(); - // sendPacket.persistent = true; - // QDEBUG() << "Persistent connection. Loop and wait."; - // continue; - // } - } // end while connected if (dtlsAssociation->closeRequest) { clientConnection->close(); @@ -278,15 +234,10 @@ void Dtlsthread::receivedDatagram(QByteArray plainText){ recPacket.fromIP = dtlsAssociation->crypto.peerAddress().toString(); recPacket.fromPort = dtlsAssociation->crypto.peerPort(); QString massageFromTheOtherPeer = QString::fromUtf8(plainText); - recPacket.hexString = recPacket.ASCIITohex(massageFromTheOtherPeer); - - //recPacket.hexString = massageFromTheOtherPeer; recPacket.toIP = dtlsAssociation->socket.localAddress().toString(); recPacket.port = dtlsAssociation->socket.localPort(); - //recPacket.errorString = "none"; recPacket.tcpOrUdp = "DTLS"; - emit packetReceived(recPacket); } @@ -316,35 +267,17 @@ void Dtlsthread::sendPersistant(Packet sendpacket) sendpacket.fromPort = clientConnection->localPort(); sendpacket.tcpOrUdp = "DTLS"; - //emit packetSent(sendpacket); } } void Dtlsthread::onTimeout(){ dtlsAssociation->closeRequest = true; - //closeRequest = true; timer->stop(); if(!(dtlsAssociation->crypto.isConnectionEncrypted())){ - QString errors = dtlsAssociation->crypto.dtlsErrorString(); - //QString sslErrorsString = errors.join(" "); + //QString errors = dtlsAssociation->crypto.dtlsErrorString(); sendpacket.errorString = "Error " + dtlsAssociation->packetToSend.errorString /*+ errors*/ ; emit packetSent(sendpacket); } - - -// if(!(dtlsAssociation->crypto.isConnectionEncrypted())){ -// createErrorPacket(){ - -// } -// } - - //dtlsAssociation->crypto.abortHandshake(&(dtlsAssociation->socket)); - //dtlsAssociation->socket.close(); - //dtlsAssociation->deleteLater(); - - //dtlsAssociation->socket.waitForDisconnected(100); - //quit(); - } DtlsAssociation* Dtlsthread::initDtlsAssociation(){ @@ -363,7 +296,6 @@ DtlsAssociation* Dtlsthread::initDtlsAssociation(){ connect(dtlsAssociationP, &DtlsAssociation::receivedDatagram, this, &Dtlsthread::receivedDatagram); PacketNetwork *parentNetwork = qobject_cast(parent()); connect(this, SIGNAL(packetReceived(Packet)), parentNetwork, SLOT(toTrafficLog(Packet))); - //dtlsAssociationP->setCipher(cmdComponents[6]); dtlsAssociationP->setCipher(settings.value("cipher", "cipher suit doesn't found").toString()); return dtlsAssociationP; @@ -371,23 +303,4 @@ DtlsAssociation* Dtlsthread::initDtlsAssociation(){ -/////////////////////////backups/////////////////// -//void Dtlsthread::addServerResponse(const QString &clientAddress, const QByteArray &datagram, const QByteArray &plainText, QHostAddress serverAddress, quint16 serverPort, quint16 userPort) -//{ -// //ned to fix the "to port" field -// //find a way do reach the client data (maybe use the clientInfo) inorder to present the "ToAddress" and "To Port" fields in the traffic log area -// //QStringList clientIpAndPort = clientInfo.split(':', Qt::KeepEmptyParts); - -// Packet recPacket; -// recPacket.init(); -// recPacket.fromIP = serverAddress.toString(); -// recPacket.fromPort = serverPort; -// QString massageFromTheOtherPeer = QString::fromUtf8(plainText); -// recPacket.hexString = massageFromTheOtherPeer; -// recPacket.toIP = clientAddress; -// recPacket.port = userPort; -// recPacket.errorString = "none"; -// recPacket.tcpOrUdp = "DTLS"; -// //emit packetReceived(recPacket); -//} diff --git a/src/packetnetwork.cpp b/src/packetnetwork.cpp index a13da9d5..a99f726d 100755 --- a/src/packetnetwork.cpp +++ b/src/packetnetwork.cpp @@ -163,7 +163,6 @@ bool PacketNetwork::DTLSListening() foreach(dtls, dtlsServers) { QDEBUGVAR(dtls->state()); if(dtls->state() == QAbstractSocket::BoundState) { - //if(udp->state() == QAbstractSocket::ConnectedState) { return true; } } @@ -177,7 +176,6 @@ bool PacketNetwork::UDPListening() foreach(udp, udpServers) { QDEBUGVAR(udp->state()); if(udp->state() == QAbstractSocket::BoundState) { - //if(udp->state() == QAbstractSocket::ConnectedState) { return true; } } @@ -288,19 +286,12 @@ void PacketNetwork::init() -// connect(&dtlsServer, SIGNAL(serverPacketReceived(Packet)), this, SLOT(packetReceivedECHO(Packet)),Qt::UniqueConnection); -// connect(&dtlsServer,&DtlsServer::serverDatagramReceived,&dtlsServer,&DtlsServer::serverReceivedDatagram,Qt::UniqueConnection); + foreach (dtlsPort, dtlsPortList) { - /////////////////////////////////after adding the DtlsServer class////////////////// bool bindResult = dtlsServer.listen(IPV4_OR_IPV6, dtlsPort); dtlsSocket = &(dtlsServer.serverSocket); -// dtlsSocket = new QUdpSocket(this); - -// bool bindResult = dtlsSocket->bind( -// IPV4_OR_IPV6 -// , dtlsPort); dtlsSocket->setSocketOption(QAbstractSocket::MulticastTtlOption, 128); @@ -326,10 +317,6 @@ void PacketNetwork::init() } if(bindResult) { -// dtlsServers.append(dtlsSocket); -////////////////////////////////////////after adding the DtlsServer class////////////////// -// QUdpSocket dtlsSocket = dtlsServer.serverSocket; -// dtlsServers.append(&dtlsSocket); dtlsServers.append(dtlsSocket); } @@ -456,31 +443,6 @@ void PacketNetwork::init() foreach (dtlsSocket, dtlsServers) { connect(&dtlsServer, SIGNAL(serverPacketReceived(Packet)), this, SLOT(packetReceivedECHO(Packet)),Qt::UniqueConnection); connect(&dtlsServer, SIGNAL(serverPacketSent(Packet)), this, SLOT(packetSentECHO(Packet)),Qt::UniqueConnection); - - //connect(&dtlsServer,&DtlsServer::serverDatagramReceived,&dtlsServer,&DtlsServer::serverReceivedDatagram,Qt::UniqueConnection); -// QUdpSocket *udpSocket, *dtlsSocket; -// ThreadedTCPServer *ssl, *tcp; - - - -// connect(&dtlsServer, SIGNAL(serverPacketReceived(Packet)), this, SLOT(packetReceivedECHO(Packet))); -// connect(&dtlsServer,&DtlsServer::serverDatagramReceived,&dtlsServer,&DtlsServer::serverReceivedDatagram); -// foreach (dtlsPort, dtlsPortList) { -// /////////////////////////////////after adding the DtlsServer class////////////////// -// bool bindResult = dtlsServer.listen(IPV4_OR_IPV6, dtlsPort); - - -// dtlsSocket = &(dtlsServer.serverSocket); -/////////////////////////////////after adding the DtlsServer class////////////////// - -// connect(&dtlsSocket, &DtlsServer::errorMessage, this, &MainWindow::addErrorMessage); -// connect(&dtlsSocket, &DtlsServer::warningMessage, this, &MainWindow::addWarningMessage); -// connect(&dtlsSocket, &DtlsServer::infoMessage, this, &MainWindow::addInfoMessage); -// connect(&dtlsSocket, &DtlsServer::datagramReceived, this, &MainWindow::addClientMessage); - -// QDEBUG() << "signal/slot datagram connect: " << connect(dtlsSocket, SIGNAL(readyRead()), -// this, SLOT(readPendingDatagrams())); - } } else { @@ -542,14 +504,9 @@ QList PacketNetwork::getDTLSPortsBound() { QList pList; pList.clear(); - //DtlsServer dtlsServer; QUdpSocket * dtlsServer; - //= &(dtlsServer.serverSocket); foreach (dtlsServer, dtlsServers) { if(dtlsServer->BoundState == QAbstractSocket::BoundState) { -// if(dtls->localAddress().isMulticast()) { -// QDEBUG() << "This udp address is multicast"; -// } if(dtlsServer){ pList.append(dtlsServer->localPort()); } @@ -751,7 +708,6 @@ void PacketNetwork::readPendingDatagrams() senderPort = theDatagram.senderPort(); QDEBUG() << "data size is" << datagram.size(); - // QDEBUG() << debugQByteArray(datagram); Packet udpPacket; udpPacket.timestamp = QDateTime::currentDateTime(); @@ -974,8 +930,6 @@ void PacketNetwork::packetToSend(Packet sendpacket) if(sendpacket.isDTLS()){ - //QSettings settings(SETTINGSFILE, QSettings::IniFormat); - //settings.setValue("packetIPEditSession", ui->packetIPEdit->text()); Dtlsthread * thread = new Dtlsthread(sendpacket, this); QSettings settings(SETTINGSFILE, QSettings::IniFormat); QDEBUG() << connect(thread, SIGNAL(packetReceived(Packet)), this, SLOT(packetReceivedECHO(Packet))) @@ -1291,8 +1245,6 @@ std::vector PacketNetwork::getCmdInput(Packet sendpacket, QSettings& se void PacketNetwork::on_twoVerify_StateChanged(){ - //ui.checkBox->setChecked(checkBoxState); - QSettings settings(SETTINGSFILE, QSettings::IniFormat); QString twoVerify = settings.value("twoVerify", "false").toString(); if(twoVerify == "false"){ @@ -1306,23 +1258,5 @@ void PacketNetwork::on_twoVerify_StateChanged(){ } } -//void PacketNetwork::addServerResponse(const QString &clientAddress, const QByteArray &datagram, const QByteArray &plainText, QHostAddress serverAddress, quint16 serverPort, quint16 userPort) -//{ -// //ned to fix the "to port" field -// //find a way do reach the client data (maybe use the clientInfo) inorder to present the "ToAddress" and "To Port" fields in the traffic log area -// //QStringList clientIpAndPort = clientInfo.split(':', Qt::KeepEmptyParts); - -// Packet recPacket; -// recPacket.init(); -// recPacket.fromIP = serverAddress.toString(); -// recPacket.fromPort = serverPort; -// QString massageFromTheOtherPeer = QString::fromUtf8(plainText); -// recPacket.hexString = massageFromTheOtherPeer; -// recPacket.toIP = clientAddress; -// recPacket.port = userPort; -// recPacket.errorString = "none"; -// recPacket.tcpOrUdp = "DTLS"; - -// emit packetReceived(recPacket); -//} + diff --git a/src/settings.cpp b/src/settings.cpp index 489ae808..06c81492 100755 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -100,22 +100,14 @@ Settings::Settings(QWidget *parent, MainWindow* mw) : ui->multiSendDelayLabel->hide(); ui->multiSendDelayEdit->hide(); - - ////////////////////////////////////////////////////////////////////////////set the initial value of the 3 pathes initialSslLocalCertificatePath = settings.value("sslLocalCertificatePath", "").toString(); initialSslCaPath = settings.value("sslCaPath", "").toString(); initialSslPrivateKeyPath = settings.value("sslPrivateKeyPath", "").toString(); -////////////////////////////////////////////////connect signal of "settins" to a slot of "mainwindow->packetnetwork->dtlsserver"/////////////////// - //MainWindow *mainWindow = qobject_cast(parent); MainWindow *mainWindow = dynamic_cast(parent); - //DtlsServer dtlsServer = mainWindow->packetNetwork.dtlsServer; DtlsServer& dtlsServer = mainWindow->packetNetwork.dtlsServer; connect(this, &Settings::loadingCertsAgain, &dtlsServer, &DtlsServer::on_signedCert_textChanged); -///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - //connect(sendSimpleAck, &QCheckBox::clicked, this, &MainWindow::on_loadKeyButton_clicked); if(settings.value("sendSimpleAck").toString() == "false"){ ui->sendSimpleAck->setChecked(false); @@ -124,12 +116,8 @@ Settings::Settings(QWidget *parent, MainWindow* mw) : } sendSimpleAck = ui->sendSimpleAck; - //MainWindow *mainWindow = qobject_cast(parent); connect(sendSimpleAck, &QCheckBox::toggled, dynamic_cast(parent), &MainWindow::on_sendSimpleAck_StateChanged); -// PacketNetwork *parentNetwork = qobject_cast(parent()); -// connect(this, SIGNAL(packetReceived(Packet)), parentNetwork, SLOT(toTrafficLog(Packet))); - QIcon mIcon(":pslogo.png"); setWindowTitle("Packet Sender "+tr("Settings")); @@ -551,11 +539,6 @@ void Settings::on_buttonBox_accepted() ENCODEMACROSAVE(3); ENCODEMACROSAVE(4); ENCODEMACROSAVE(5); - ////////////////////////////////////////////////////////////////////// - /// -// initialSslLocalCertificatePath = settings.value("sslLocalCertificatePath", "").toString(); -// initialSslCaPath = settings.value("sslCaPath", "").toString(); -// initialSslPrivateKeyPath = settings.value("sslPrivateKeyPath", "").toString(); if((initialSslLocalCertificatePath != settings.value("sslLocalCertificatePath", "").toString()) || (initialSslCaPath != settings.value("sslCaPath", "").toString()) || @@ -568,19 +551,6 @@ void Settings::on_buttonBox_accepted() } - - -// ////////////////////////////////////////////////connect signal of "settins" to a slot of "mainwindow->packetnetwork->dtlsserver"/////////////////// -// sslLocalCertificatePath = ui->sslLocalCertificatePath; -// //MainWindow *mainWindow = qobject_cast(parent); -// //MainWindow *mainWindow = dynamic_cast(parent); -// //DtlsServer dtlsServer = mainWindow->packetNetwork.dtlsServer; -// DtlsServer& dtlsServer = rmw->packetNetwork.dtlsServer; - -// connect(sslLocalCertificatePath, &QLineEdit::textChanged, &dtlsServer, &DtlsServer::on_signedCert_textChanged); -// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - } void Settings::on_asciiResponseEdit_textEdited(const QString &arg1) @@ -820,16 +790,6 @@ void Settings::on_sslLocalCertificatePathBrowseButton_clicked() ui->sslLocalCertificatePath->setText(fileName); } -// ////////////////////////////////////////////////connect signal of "settins" to a slot of "mainwindow->packetnetwork->dtlsserver"/////////////////// -// sslLocalCertificatePath = ui->sslLocalCertificatePath; -// //MainWindow *mainWindow = qobject_cast(parent); -// //MainWindow *mainWindow = dynamic_cast(parent); -// //DtlsServer dtlsServer = mainWindow->packetNetwork.dtlsServer; -// DtlsServer& dtlsServer = rmw->packetNetwork.dtlsServer; - -// connect(sslLocalCertificatePath, &QLineEdit::textChanged, &dtlsServer, &DtlsServer::on_signedCert_textChanged); -// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - } void Settings::on_sslPrivateKeyPathBrowseButton_clicked() From 0af89979393b7497ebd2c04b72b28e69d94caf43 Mon Sep 17 00:00:00 2001 From: israel Date: Tue, 26 Dec 2023 21:12:46 +0200 Subject: [PATCH 79/79] server invalid error massage fixed --- src/dtlsserver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dtlsserver.cpp b/src/dtlsserver.cpp index 96cc8e90..c7feb777 100644 --- a/src/dtlsserver.cpp +++ b/src/dtlsserver.cpp @@ -149,7 +149,7 @@ void DtlsServer::readyRead() if (sendResponse || !smartData.isEmpty()) { if(serverResonse(client->get())){ - QMessageBox::critical(nullptr, "Connection Error", "server response can't be sent."); +// QMessageBox::critical(nullptr, "Connection Error", "server response can't be sent."); } }