diff --git a/Client/QChatRoomMainWindow.cpp b/Client/QChatRoomMainWindow.cpp index 071e95f..bdb9442 100644 --- a/Client/QChatRoomMainWindow.cpp +++ b/Client/QChatRoomMainWindow.cpp @@ -14,124 +14,128 @@ #include #include #include +#include #include "QServerConnection.h" #include "QChatlisMenuBar.h" +#include "QChatWidget.h" QChatRoomMainWindow::QChatRoomMainWindow(QWidget* parent) - : QMainWindow(parent), serverConnection{ new QServerConnection(this) } + : QMainWindow(parent), client(true), serverConnection{ new QServerConnection(this) }, + userDisplayName{}, participantsPanel{}, chatWidget{ new QChatWidget(this, client) } { - QWidget* centralWidget{ new QWidget }; - QVBoxLayout* centralLayout{ new QVBoxLayout }; - QChatlisMenuBar* topMenuBar{ new QChatlisMenuBar(this) }; setMenuBar(topMenuBar); - QSplitter* topLayout{ new QSplitter }; - topLayout->setChildrenCollapsible(false); - QChatbox* chatbox{ new QChatbox(this) }; - QWidget* usersWidget{ new QWidget }; - QVBoxLayout* usersLayout{ new QVBoxLayout }; - QLabel* userLabel{ new QLabel("Your name: " + serverConnection->getUsername() + '@' + serverConnection->getComputerName()) }; - QParticipantsPanel* participantsPanel{ new QParticipantsPanel }; - usersLayout->addWidget(userLabel); - usersLayout->addWidget(participantsPanel); - usersWidget->setLayout(usersLayout); - topLayout->addWidget(chatbox); - topLayout->addWidget(usersWidget); - - QHBoxLayout* textInputLayout{ new QHBoxLayout }; - QLabel* textInputLabel{ new QLabel(tr("Message :")) }; - QLineEdit* textInput{ new QLineEdit }; - textInputLayout->addWidget(textInputLabel); - textInputLayout->addWidget(textInput); - - centralLayout->addWidget(topLayout); - centralLayout->addLayout(textInputLayout); - centralWidget->setLayout(centralLayout); - setCentralWidget(centralWidget); - - connect(topMenuBar, &QChatlisMenuBar::actionConnectToServer, this, &QChatRoomMainWindow::tryConnectToServer); - connect(topMenuBar, &QChatlisMenuBar::actionDisconnectFromServer, this, &QChatRoomMainWindow::tryDisconnectFromServer); - connect(topMenuBar, &QChatlisMenuBar::actionChangeUsername, this, &QChatRoomMainWindow::tryChangeUsername); - connect(topMenuBar, &QChatlisMenuBar::actionChangeComputerName, this, &QChatRoomMainWindow::tryChangeComputerName); - - connect(textInput, &QLineEdit::returnPressed, [=]() { - QString currentText(textInput->text().simplified()); - if (!currentText.isEmpty()) - { - chatbox->appendUserMessage(serverConnection->getUsername(), currentText); - serverConnection->sendNewChatMessage(currentText); - } - else - chatbox->appendSystemMessage("Can't send empty messages -_-"); - - textInput->clear(); - }); + QSplitter* splitter{ new QSplitter(this) }; + splitter->setChildrenCollapsible(false); + + splitter->addWidget(chatWidget); + splitter->addWidget(initParticipantsWidget()); + + resize(1000, 400); + setCentralWidget(splitter); + setWindowTitle("Chatlis"); + setWindowIcon(QIcon("group-chat.png")); + + connect(topMenuBar, &QChatlisMenuBar::actionConnectToServer, this, &QChatRoomMainWindow::actionConnectToServer); + connect(topMenuBar, &QChatlisMenuBar::actionDisconnectFromServer, this, &QChatRoomMainWindow::actionDisconnectFromServer); + connect(topMenuBar, &QChatlisMenuBar::actionChangeUsername, this, &QChatRoomMainWindow::actionChangeUsername); + connect(topMenuBar, &QChatlisMenuBar::actionChangeComputerName, this, &QChatRoomMainWindow::actionChangeComputerName); + + connect(serverConnection, &QServerConnection::appendSystemMessage, chatWidget, &QChatWidget::appendSystemMessage); + connect(serverConnection, &QServerConnection::addMessageToChatbox, chatWidget, &QChatWidget::appendUserMessage); + connect(serverConnection, &QServerConnection::appendServerMessage, chatWidget, &QChatWidget::appendServerMessage); + connect(chatWidget, &QChatWidget::sendMessage, serverConnection, &QServerConnection::sendNewChatMessage); - connect(serverConnection, &QServerConnection::clearChatbox, chatbox, &QChatbox::clearChat); - connect(serverConnection, &QServerConnection::appendSystemMessage, chatbox, &QChatbox::appendSystemMessage); - connect(serverConnection, &QServerConnection::addMessageToChatbox, chatbox, &QChatbox::appendUserMessage); - connect(serverConnection, &QServerConnection::appendServerMessage, chatbox, &QChatbox::appendServerMessage); connect(serverConnection, &QServerConnection::newClient, participantsPanel, &QParticipantsPanel::addParticipant); connect(serverConnection, &QServerConnection::serverDisconnected, participantsPanel, &QParticipantsPanel::clear); - connect(serverConnection, &QServerConnection::clientChangedUsername, this, [=](const QString& newUsername) { - userLabel->setText("Your name: " + newUsername + '@' + serverConnection->getComputerName()); - }); - connect(serverConnection, &QServerConnection::clientChangedComputerName, this, [=](const QString& newComputerName) { - userLabel->setText("Your name: " + serverConnection->getUsername() + '@' + newComputerName); - }); connect(serverConnection, &QServerConnection::otherClientChangedUsername, participantsPanel, &QParticipantsPanel::otherClientChangedUsername); connect(serverConnection, &QServerConnection::otherClientChangedComputerName, participantsPanel, &QParticipantsPanel::otherClientChangedComputerName); - - connect(serverConnection, &QServerConnection::removeClient, participantsPanel, &QParticipantsPanel::removeParticipant); - resize(650, 350); - setCentralWidget(centralWidget); - setWindowTitle("Chatlis"); - setWindowIcon(QIcon("group-chat.png")); } -void QChatRoomMainWindow::tryConnectToServer() +void QChatRoomMainWindow::actionConnectToServer() { - QString serverAddress(QInputDialog::getText(this, - tr("Chatlis server connection"), - tr("Server address (ip:port)"))); + QString serverAddress(QInputDialog::getText(this, tr("Chatlis server connection"), tr("Server address (ip:port)"))); if (!serverAddress.isEmpty()) { QStringList ipAndPort = serverAddress.split(':'); if (ipAndPort.size() == 2 && !ipAndPort[0].isEmpty() && !ipAndPort[1].isEmpty()) - serverConnection->connectToServer(ipAndPort[0], ipAndPort[1]); + { + chatWidget->clearMessages(); + serverConnection->connectToServer(ipAndPort[0], ipAndPort[1], client.getUsername(), client.getComputerName()); + } else QMessageBox::critical(this, "Wrong format", "Address and port of Chatlis server should be XXX.XXX.XXX.XXX:PPPPP"); } } -void QChatRoomMainWindow::tryDisconnectFromServer() +void QChatRoomMainWindow::actionDisconnectFromServer() { serverConnection->disconnectFromHost(); } -void QChatRoomMainWindow::tryChangeUsername() +void QChatRoomMainWindow::actionChangeUsername() { - QString newUsername(QInputDialog::getText(this, - tr("Change username"), - tr("Username to display"))); + QString newUsername(QInputDialog::getText(this, tr("Change username"), tr("Username to display"))); if (!newUsername.isEmpty()) - serverConnection->changeUserName(newUsername); + { + if (newUsername.size() > 20) + QMessageBox::critical(this, "Username too long", "Displayed username can't be longer than 20"); + else + { + client.setUsername(newUsername); + serverConnection->changeUserName(newUsername); + userDisplayName->setText(newUsername + '@' + client.getComputerName()); + } + } } -void QChatRoomMainWindow::tryChangeComputerName() +void QChatRoomMainWindow::actionChangeComputerName() { - QString newComputerName(QInputDialog::getText(this, - tr("Change computer name"), - tr("Computer name to display"))); + QString newComputerName(QInputDialog::getText(this, tr("Change computer name"), tr("Computer name to display"))); if (!newComputerName.isEmpty()) - serverConnection->changeComputerName(newComputerName); + { + if (newComputerName.size() > 20) + QMessageBox::critical(this, "Computer name too long", "Displayed computer name can't be longer than 20"); + else + { + client.setComputerName(newComputerName); + serverConnection->changeComputerName(newComputerName); + userDisplayName->setText(client.getUsername() + '@' + newComputerName); + } + } +} + +QWidget* QChatRoomMainWindow::initParticipantsWidget() +{ + QWidget* usersWidget{ new QWidget }; + QSizePolicy modifiedPolicy{ usersWidget->sizePolicy() }; + modifiedPolicy.setHorizontalStretch(1); + usersWidget->setSizePolicy(modifiedPolicy); + QVBoxLayout* usersLayout{ new QVBoxLayout }; + + QGroupBox* userDisplayNameGroupBox{ new QGroupBox("Your name") }; + QVBoxLayout* userDisplayNameLayout{ new QVBoxLayout }; + userDisplayName = new QLabel(client.getUsername() + '@' + client.getComputerName()); + userDisplayNameLayout->addWidget(userDisplayName); + userDisplayNameGroupBox->setLayout(userDisplayNameLayout); + + QGroupBox* participantsGroupBox{ new QGroupBox("Participants") }; + QVBoxLayout* participantsLayout{ new QVBoxLayout }; + participantsPanel = new QParticipantsPanel; + participantsLayout->addWidget(participantsPanel); + participantsGroupBox->setLayout(participantsLayout); + + usersLayout->addWidget(userDisplayNameGroupBox); + usersLayout->addWidget(participantsGroupBox); + usersWidget->setLayout(usersLayout); + return usersWidget; } QChatRoomMainWindow::~QChatRoomMainWindow() { delete serverConnection; -} +} \ No newline at end of file diff --git a/Client/QChatRoomMainWindow.h b/Client/QChatRoomMainWindow.h index 86d4afe..5867d00 100644 --- a/Client/QChatRoomMainWindow.h +++ b/Client/QChatRoomMainWindow.h @@ -1,10 +1,13 @@ #pragma once #include -#include "QChatbox.h" +#include "QChatWidget.h" #include #include #include "QServerConnection.h" +#include "QParticipantsPanel.h" +#include +#include class QChatRoomMainWindow : public QMainWindow { @@ -15,11 +18,17 @@ class QChatRoomMainWindow : public QMainWindow ~QChatRoomMainWindow(); private slots: - void tryConnectToServer(); - void tryDisconnectFromServer(); - void tryChangeUsername(); - void tryChangeComputerName(); + void actionConnectToServer(); + void actionDisconnectFromServer(); + void actionChangeUsername(); + void actionChangeComputerName(); private: + QClientInfo client; + QServerConnection* serverConnection; + QLabel* userDisplayName; + QParticipantsPanel* participantsPanel; + QChatWidget* chatWidget; + QWidget* initParticipantsWidget(); }; diff --git a/Client/QChatWidget.cpp b/Client/QChatWidget.cpp new file mode 100644 index 0000000..df55a60 --- /dev/null +++ b/Client/QChatWidget.cpp @@ -0,0 +1,55 @@ +#include "QChatWidget.h" +#include +#include +#include +#include +#include + +QChatWidget::QChatWidget(QWidget *parent, const QClientInfo& clientInfo) + : QWidget(parent), chatBox{ new QChatbox(this) }, textInput{ new QLineEdit(this) }, clientInfo(clientInfo) +{ + QSizePolicy modifiedPolicy{ sizePolicy() }; + modifiedPolicy.setHorizontalStretch(2); + setSizePolicy(modifiedPolicy); + + QVBoxLayout* mainLayout{ new QVBoxLayout }; + + QGroupBox* chatBoxGroupBox{ new QGroupBox("Chat") }; + QVBoxLayout* chatBoxGroupBoxLayout{ new QVBoxLayout }; + chatBoxGroupBoxLayout->addWidget(chatBox); + chatBoxGroupBox->setLayout(chatBoxGroupBoxLayout); + + QHBoxLayout* textInputLayout{ new QHBoxLayout }; + QPushButton* sendMessageButton{ new QPushButton("Send", this) }; + textInputLayout->addWidget(textInput); + textInputLayout->addWidget(sendMessageButton); + + mainLayout->addWidget(chatBoxGroupBox); + mainLayout->addLayout(textInputLayout); + setLayout(mainLayout); + + connect(this, &QChatWidget::appendSystemMessage, chatBox, &QChatbox::appendSystemMessage); + connect(this, &QChatWidget::appendUserMessage, chatBox, &QChatbox::appendUserMessage); + connect(this, &QChatWidget::appendServerMessage, chatBox, &QChatbox::appendServerMessage); + + connect(sendMessageButton, &QPushButton::clicked, textInput, &QLineEdit::returnPressed); + connect(textInput, &QLineEdit::returnPressed, this, &QChatWidget::handleReturnPressed); +} + +void QChatWidget::handleReturnPressed() +{ + QString currentText(textInput->text().simplified()); + if (!currentText.isEmpty()) + { + appendUserMessage(clientInfo.getUsername(), currentText); + emit sendMessage(currentText); + } + textInput->clear(); +} + +void QChatWidget::clearMessages() +{ + chatBox->clear(); +} + +QChatWidget::~QChatWidget() {} \ No newline at end of file diff --git a/Client/QChatWidget.h b/Client/QChatWidget.h new file mode 100644 index 0000000..b5b39a9 --- /dev/null +++ b/Client/QChatWidget.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include "QChatbox.h" +#include +#include "../QClientInfo.h" + +class QChatWidget : public QWidget +{ + Q_OBJECT + +public: + QChatWidget(QWidget* parent, const QClientInfo& clientInfo); + ~QChatWidget(); + + void clearMessages(); + +signals: + void appendUserMessage(const QString& from, const QString& message); + void appendSystemMessage(const QString message); + void appendServerMessage(const QString message); + void sendMessage(const QString& message); + +private slots: + void handleReturnPressed(); + +private: + QChatbox* chatBox; + QLineEdit* textInput; + const QClientInfo& clientInfo; +}; diff --git a/Client/QChatbox.cpp b/Client/QChatbox.cpp index 80969b0..d0a4e5c 100644 --- a/Client/QChatbox.cpp +++ b/Client/QChatbox.cpp @@ -14,10 +14,6 @@ const QColor QChatbox::SYSTEM_MESSAGE_TEXT_COLOR{ Qt::red }; QChatbox::QChatbox(QWidget *parent) : QTextEdit(parent) { - QSizePolicy modifiedPolicy{ sizePolicy() }; - modifiedPolicy.setHorizontalStretch(4); - setSizePolicy(modifiedPolicy); - setFocusPolicy(Qt::NoFocus); setReadOnly(true); } @@ -54,9 +50,4 @@ void QChatbox::appendUserMessage(const QString& from, const QString& message) bar->setValue(bar->maximum()); } -void QChatbox::clearChat() -{ - clear(); -} - QChatbox::~QChatbox() {} \ No newline at end of file diff --git a/Client/QChatbox.h b/Client/QChatbox.h index 61fe537..84dcf09 100644 --- a/Client/QChatbox.h +++ b/Client/QChatbox.h @@ -12,10 +12,8 @@ class QChatbox : public QTextEdit QChatbox(QWidget *parent = nullptr); ~QChatbox(); - void appendUserMessage(const QString& from, const QString& message); - public slots: - void clearChat(); + void appendUserMessage(const QString& from, const QString& message); void appendSystemMessage(const QString message); void appendServerMessage(const QString message); diff --git a/Client/QParticipantsPanel.cpp b/Client/QParticipantsPanel.cpp index 417ad4c..dbe5e04 100644 --- a/Client/QParticipantsPanel.cpp +++ b/Client/QParticipantsPanel.cpp @@ -5,10 +5,6 @@ QParticipantsPanel::QParticipantsPanel(QWidget* parent) : QListView(parent), model{} { - QSizePolicy modifiedPolicy{ sizePolicy() }; - modifiedPolicy.setHorizontalStretch(1); - setSizePolicy(modifiedPolicy); - model = new QStringListModel(this); setModel(model); setEditTriggers(QAbstractItemView::NoEditTriggers); diff --git a/Client/QServerConnection.cpp b/Client/QServerConnection.cpp index 3d65743..a197644 100644 --- a/Client/QServerConnection.cpp +++ b/Client/QServerConnection.cpp @@ -11,66 +11,59 @@ #include QServerConnection::QServerConnection(QObject* parent) - : QSslSocket(parent), client(this) + : QSslSocket(parent) { - - QFile caFile("../../SSL/rootCA.pem"); - caFile.open(QIODevice::ReadOnly); - QSslCertificate caCert = QSslCertificate(caFile.readAll()); - caFile.close(); - + auto caCerts = QSslCertificate::fromPath("SSL/ca/*.pem", QSsl::Pem, QSslCertificate::PatternSyntax::Wildcard); + Q_ASSERT(!caCerts.isEmpty()); QSslConfiguration config; - config.addCaCertificate(caCert); + config.setCaCertificates(caCerts); setSslConfiguration(config); - //ingore bad hostname + auto serverCerts = QSslCertificate::fromPath("SSL/public/*.pem", QSsl::Pem, QSslCertificate::PatternSyntax::Wildcard); + Q_ASSERT(!serverCerts.isEmpty()); QList errorsToIgnore; - const QString serverCertPath("../../SSL/client1.pem"); - auto serverCert = QSslCertificate::fromPath(serverCertPath); - Q_ASSERT(!serverCert.isEmpty()); - errorsToIgnore << QSslError(QSslError::HostNameMismatch, serverCert.at(0)); + for (const auto& cert : serverCerts) + errorsToIgnore << QSslError(QSslError::HostNameMismatch, cert); ignoreSslErrors(errorsToIgnore); connect(this, &QIODevice::readyRead, this, &QServerConnection::receivedData); connect(this, &QAbstractSocket::disconnected, this, &QServerConnection::notifyDisconnection); - connect(&client, &QClientInfo::usernameChanged, this, &QServerConnection::clientChangedUsername); - connect(&client, &QClientInfo::computerNameChanged, this, &QServerConnection::clientChangedComputerName); -} - -QString QServerConnection::getUsername() const -{ - return client.getUsername(); } -QString QServerConnection::getComputerName() const +void QServerConnection::connectToServer(const QString& address, const QString& portNb, const QString& username, const QString& computerName) { - return client.getComputerName(); -} + if (isEncrypted()) + { + disconnectFromHost(); + waitForDisconnected(5000); + } -void QServerConnection::connectToServer(const QString& address, const QString& portNb) -{ - if (!address.isEmpty()) { - emit clearChatbox(); + if (!address.isEmpty()) + { connectToHostEncrypted(address, portNb.toUInt()); - if (waitForConnected()) { - emit appendSystemMessage("Connected to " + peerAddress().toString()); - if (waitForEncrypted()) { + emit appendSystemMessage("Connecting to " + address + ':' + portNb); + if (waitForConnected(10000)) + { + emit appendSystemMessage("Connected to " + address + ':' + portNb); + emit appendSystemMessage("Trying to set up encryption"); + if (waitForEncrypted(10000)) + { emit appendSystemMessage("Encrypted connection established"); QByteArray buffer; QDataStream dataStream(&buffer, QIODevice::WriteOnly); - dataStream << NetworkMessage::Type::clientRegistration << client.getUsername() << client.getComputerName(); + dataStream << NetworkMessage::Type::clientRegistration << username << computerName; QDataStream dataToSend(this); dataToSend << buffer; } - else { + else + { emit appendSystemMessage("Encryption failed. Disconnecting"); disconnectFromHost(); } } - else { + else emit appendSystemMessage("Connection failed"); - } } } @@ -85,8 +78,6 @@ void QServerConnection::sendNewChatMessage(const QString& message) void QServerConnection::changeUserName(const QString& newUsername) { - client.setUsername(newUsername); - QByteArray buffer; QDataStream dataStream(&buffer, QIODevice::WriteOnly); dataStream << NetworkMessage::Type::clientChangeUsername << newUsername; @@ -96,8 +87,6 @@ void QServerConnection::changeUserName(const QString& newUsername) void QServerConnection::changeComputerName(const QString& newComputerName) { - client.setComputerName(newComputerName); - QByteArray buffer; QDataStream dataStream(&buffer, QIODevice::WriteOnly); dataStream << NetworkMessage::Type::clientChangeComputerName << newComputerName; diff --git a/Client/QServerConnection.h b/Client/QServerConnection.h index e11c135..8e738e2 100644 --- a/Client/QServerConnection.h +++ b/Client/QServerConnection.h @@ -14,14 +14,12 @@ class QServerConnection : public QSslSocket QServerConnection(QObject *parent); ~QServerConnection(); - QString getUsername() const; - QString getComputerName() const; - - void connectToServer(const QString& address, const QString& portNb); - void sendNewChatMessage(const QString& message); + void connectToServer(const QString& address, const QString& portNb, const QString& username, const QString& computerName); void changeUserName(const QString& newUsername); void changeComputerName(const QString& newComputerName); +public slots: + void sendNewChatMessage(const QString& message); signals: void clearChatbox(); @@ -30,18 +28,15 @@ class QServerConnection : public QSslSocket void removeClient(const QString username, const QString computerName); void appendSystemMessage(const QString message); void appendServerMessage(const QString message); - void serverDisconnected(); - void clientChangedUsername(const QString& newUsername); - void clientChangedComputerName(const QString& newComputerName); void otherClientChangedUsername(const QString previousUsername, const QString computerName, const QString newUsername); void otherClientChangedComputerName(const QString username, const QString previousComputerName, const QString newComputerName); + void serverDisconnected(); private slots: void receivedData(); void notifyDisconnection(); private: - QClientInfo client; QSslKey key; QSslCertificate cert; }; \ No newline at end of file diff --git a/Client/main.cpp b/Client/main.cpp index 02dc075..c667d32 100644 --- a/Client/main.cpp +++ b/Client/main.cpp @@ -1,14 +1,8 @@ #include #include "QChatRoomMainWindow.h" -#include -#include int main(int argc, char* argv[]) { - QLockFile lockFile(QDir::tempPath() + "/ChatlisClient.lock"); - if (!lockFile.tryLock()) - return 0; - QApplication app(argc, argv); QChatRoomMainWindow chatWindow; diff --git a/QClientInfo.cpp b/QClientInfo.cpp index db3a39d..8796324 100644 --- a/QClientInfo.cpp +++ b/QClientInfo.cpp @@ -1,23 +1,25 @@ #include "QClientInfo.h" -QClientInfo::QClientInfo(QObject *parent) - : QObject(parent), computerName(qEnvironmentVariable("USERDOMAIN")), - username(qEnvironmentVariable("USERNAME")) +QClientInfo::QClientInfo(bool isLocalClient) + : computerName(), username() { - if (username.isEmpty()) - username = qEnvironmentVariable("USER"); + if (isLocalClient) + { + computerName = qEnvironmentVariable("USERDOMAIN"); + username = qEnvironmentVariable("USERNAME"); + if (username.isEmpty()) + username = qEnvironmentVariable("USER"); + } } void QClientInfo::setUsername(const QString& newName) { username = newName; - emit usernameChanged(newName); } void QClientInfo::setComputerName(const QString& newComputerName) { computerName = newComputerName; - emit computerNameChanged(newComputerName); } QString QClientInfo::getUsername() const diff --git a/QClientInfo.h b/QClientInfo.h index f6e1d0a..c1bd5d7 100644 --- a/QClientInfo.h +++ b/QClientInfo.h @@ -9,7 +9,7 @@ class QClientInfo : public QObject Q_OBJECT public: - QClientInfo(QObject *parent = nullptr); + QClientInfo(bool isLocalClient); ~QClientInfo(); @@ -19,10 +19,6 @@ class QClientInfo : public QObject void setUsername(const QString& newName); void setComputerName(const QString& newComputerName); -signals: - void usernameChanged(const QString& newUsername); - void computerNameChanged(const QString& newComputerName); - private: QString username; QString computerName; diff --git a/README.md b/README.md index 2df9de1..2d322c5 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,25 @@ A chatroom made in C++ with the Qt librairy. An expanded and updated version of Qt's example project : https://doc.qt.io/qt-6/qtnetwork-network-chat-example.html -## Using Chatlis w/SSL (v1.0.0 and up) +# Using Chatlis w/SSL (v1.0.0 and up) The server mainainer needs to generate an SSL certificate that is signed with a CA certificate. -#### Generate the certificates +## Port-Forwarding + +To enable internet communication, you need to port-forward TCP over port 59532. + +## Installing OpenSSL Command Prompt on Windows + +Run the following command in an elevated powershell window: + +```sh +winget install openssl +``` + +Then search for and run Win64 OpenSSL Command Prompt to enter the commands specified in the next step. + +## Generate the certificates The following example will do 3 things: @@ -20,25 +34,51 @@ The following example will do 3 things: ```sh openssl genrsa -out rootCA.key 2048 -openssl req -x509 -new -key rootCA.key -days 3650 -out rootCA.pem \ --subj '/C=AA/ST=AA/L=AA/O=AA Ltd/OU=AA/CN=AA/emailAddress=aa@aa.com' +openssl req -x509 -new -key rootCA.key -days 365 -out rootCA.pem -subj "/C=AA/ST=AA/L=AA/O=AA Ltd/OU=AA/CN=AA/emailAddress=aa@aa.com" ``` -2. Create the server certificate and key pair: +2. Create the server csr and key pair: ```sh openssl genrsa -out server.key 2048 -openssl req -new -key server.key -out server.csr \ --subj '/C=BB/ST=BB/L=BB/O=BB Ltd/OU=BB/CN=BB/emailAddress=bb@bb.com' +openssl req -new -key server.key -out server.csr -subj "/C=BB/ST=BB/L=BB/O=BB Ltd/OU=BB/CN=BB/emailAddress=bb@bb.com" ``` 3. Generate a signed certificate ```sh -openssl x509 -req -days 365 -CA rootCA.pem -CAkey rootCA.key \ --CAcreateserial -CAserial serial -in server.csr -out server.pem +openssl x509 -req -days 365 -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -CAserial serial -in server.csr -out server.pem ``` -#### Use the certifactes +## Use the certificates + +You should now have these files: + +- rootCA.key +- rootCA.pem +- server.key +- server.csr +- server.pem + +You should now delete the following files for security: +- serial +- rootCA.key +- server.csr + +*(they serve no purpose to the applciation)* + +### On the server side + +You should put `server.key` and `server.pem` in your server's `SSL` folder. + +### On the client side + +Each user wanting to connect to your server should get a copy of: + +- Your `rootCA.pem` file in their client's `SSL/ca/` folder. +- Your `server.pem` file in their client's `SSL/public/` folder. + +> Note: you should rename the copies of `rootCA.pem` and `server.pem` to memorable names when handing them out to users in case they have multiple certificates to deal with. -Coming soon... +## You're done +This should be everything you need to get the server and clients to talk to each other over SSL using a secure protocol! 👍 \ No newline at end of file diff --git a/Server/QChatlisServer.cpp b/Server/QChatlisServer.cpp index ce925b4..c11a330 100644 --- a/Server/QChatlisServer.cpp +++ b/Server/QChatlisServer.cpp @@ -3,15 +3,12 @@ #include #include "../QClientInfo.h" #include -#include -#include -#include #include const quint16 QChatlisServer::PORT_NB{ 59532 }; -QChatlisServer::QChatlisServer(QObject* parent) - : QSslServer(parent), connectedClients() +QChatlisServer::QChatlisServer() + : connectedClients() { connect(this, &QTcpServer::pendingConnectionAvailable, this, &QChatlisServer::getNextPendingConnection); listen(QHostAddress::Any, QChatlisServer::PORT_NB); @@ -19,42 +16,24 @@ QChatlisServer::QChatlisServer(QObject* parent) void QChatlisServer::incomingConnection(qintptr socketDescriptor) { - QFile keyFile("../../SSL/client1.key"); - keyFile.open(QIODevice::ReadOnly); - QSslKey privateKey = QSslKey(keyFile.readAll(), QSsl::Rsa); - keyFile.close(); - - QFile certFile("../../SSL/client1.pem"); - certFile.open(QIODevice::ReadOnly); - QSslCertificate localCert = QSslCertificate(certFile.readAll()); - certFile.close(); - - QClientConnection* newClient{ new QClientConnection(this) }; - newClient->setSocketDescriptor(socketDescriptor); - newClient->setPrivateKey(privateKey); - newClient->setLocalCertificate(localCert); - newClient->startServerEncryption(); + QClientConnection* newClient{ new QClientConnection(this, socketDescriptor) }; addPendingConnection(newClient); } void QChatlisServer::getNextPendingConnection() { - QClientConnection* newClient{ dynamic_cast(nextPendingConnection()) }; - connect(newClient, &QClientConnection::newClient, this, &QChatlisServer::replicateNewUser); connect(newClient, &QClientConnection::newClientMessage, this, &QChatlisServer::replicateClientMessage); connect(newClient, &QClientConnection::newClientName, this, &QChatlisServer::replicateClientNewUsername); connect(newClient, &QClientConnection::newClientComputerName, this, &QChatlisServer::replicateClientNewComputerName); - connect(newClient, &QAbstractSocket::disconnected, this, &QChatlisServer::clientDisconnected); - if (connectedClients.size() > 0) { QList> existingClients; for (QClientConnection* client : connectedClients) - existingClients.push_back(QPair(client->getClientUsername(), client->getClientComputerName())); + existingClients.emplace_back(client->getClientUsername(), client->getClientComputerName()); newClient->replicateExistingClients(existingClients); } @@ -69,7 +48,8 @@ void QChatlisServer::replicateNewUser() QString log("Log : connection opened with client [%1] (%2)"); - emit serverLog(log.arg(username, senderConnection->peerAddress().toString())); + QHostAddress formated(senderConnection->peerAddress().toIPv4Address()); + emit serverLog(log.arg(username, formated.toString())); for (QClientConnection* client : connectedClients) if (client != senderConnection) @@ -107,15 +87,20 @@ void QChatlisServer::replicateClientMessage(const QString message) void QChatlisServer::clientDisconnected() { QClientConnection* disconnectedClient{ dynamic_cast(sender()) }; - const QString& username = disconnectedClient->getClientUsername(); - const QString& computerName = disconnectedClient->getClientComputerName(); + + if (disconnectedClient->isEncrypted()) + { + const QString& username = disconnectedClient->getClientUsername(); + const QString& computerName = disconnectedClient->getClientComputerName(); - QString log("Log : connection closed with client %2"); - emit serverLog(log.arg(disconnectedClient->peerAddress().toString())); + QString log("Log : connection closed with client [%1] (%2)"); + QHostAddress formated(disconnectedClient->peerAddress().toIPv4Address()); + emit serverLog(log.arg(username, formated.toString())); - for (QClientConnection* client : connectedClients) - if (client != disconnectedClient) - client->replicateDisconnect(username, computerName); + for (QClientConnection* client : connectedClients) + if (client != disconnectedClient) + client->replicateDisconnect(username, computerName); + } connectedClients.removeOne(disconnectedClient); disconnectedClient->deleteLater(); diff --git a/Server/QChatlisServer.h b/Server/QChatlisServer.h index b711650..5babb3a 100644 --- a/Server/QChatlisServer.h +++ b/Server/QChatlisServer.h @@ -10,7 +10,7 @@ class QChatlisServer : public QSslServer Q_OBJECT public: - QChatlisServer(QObject* parent = nullptr); + QChatlisServer(); ~QChatlisServer(); const static quint16 PORT_NB; diff --git a/Server/QClientConnection.cpp b/Server/QClientConnection.cpp index 1f052ff..c4c1edf 100644 --- a/Server/QClientConnection.cpp +++ b/Server/QClientConnection.cpp @@ -1,13 +1,29 @@ #include "QClientConnection.h" #include "../NetworkMessage.h" #include +#include +#include +#include - -QClientConnection::QClientConnection(QObject* parent) - : QSslSocket(parent), client() +QClientConnection::QClientConnection(QObject* parent, qintptr socketDescriptor) + : QSslSocket(parent), client(false) { + QFile keyFile("SSL/server.key"); + keyFile.open(QIODevice::ReadOnly); + QSslKey privateKey = QSslKey(keyFile.readAll(), QSsl::Rsa); + keyFile.close(); + + QFile certFile("SSL/server.pem"); + certFile.open(QIODevice::ReadOnly); + QSslCertificate localCert = QSslCertificate(certFile.readAll()); + certFile.close(); + + setPrivateKey(privateKey); + setLocalCertificate(localCert); + setSocketDescriptor(socketDescriptor); + startServerEncryption(); + connect(this, &QClientConnection::readyRead, this, &QClientConnection::receivedData); - qDebug("New QClient"); } void QClientConnection::replicateExistingClients(const QList>& existingClients) diff --git a/Server/QClientConnection.h b/Server/QClientConnection.h index ca3f995..7fe69e4 100644 --- a/Server/QClientConnection.h +++ b/Server/QClientConnection.h @@ -8,7 +8,7 @@ class QClientConnection : public QSslSocket Q_OBJECT public: - QClientConnection(QObject* parent); + QClientConnection(QObject* parent, qintptr socketDescriptor); ~QClientConnection(); void replicateExistingClients(const QList>& existingClients); diff --git a/Server/QServerMainWindow.cpp b/Server/QServerMainWindow.cpp index 351b5ad..a1d6171 100644 --- a/Server/QServerMainWindow.cpp +++ b/Server/QServerMainWindow.cpp @@ -4,39 +4,55 @@ #include #include #include +#include #include QServerMainWindow::QServerMainWindow(QWidget *parent) - : QMainWindow(parent), server{ new QChatlisServer } + : QMainWindow(parent), server{ new QChatlisServer }, output{} { QWidget* centralWidget{ new QWidget }; QVBoxLayout* centralLayout{ new QVBoxLayout }; - centralLayout->setContentsMargins(10, 10, 10, 10); + + QGroupBox* serverLog{ initOutputGroupBox() }; + centralLayout->addWidget(serverLog); + centralWidget->setLayout(centralLayout); + setCentralWidget(centralWidget); + setWindowTitle("Chatlis - Server"); + resize(400, 200); + + connect(server, &QChatlisServer::serverLog, output, &QTextEdit::append); +} + + +QGroupBox* QServerMainWindow::initOutputGroupBox() +{ QGroupBox* outputGroupBox{ new QGroupBox("Output") }; outputGroupBox->setFocusPolicy(Qt::NoFocus); QVBoxLayout* outputGroupBoxLayout{ new QVBoxLayout }; - QTextEdit* output{ new QTextEdit }; + output = new QTextEdit; output->setReadOnly(true); output->setLineWrapMode(QTextEdit::NoWrap); outputGroupBoxLayout->addWidget(output); outputGroupBox->setLayout(outputGroupBoxLayout); - + QString foundIPV4Addresses; for (const QHostAddress& address : QNetworkInterface::allAddresses()) - output->append("IP address found : " + address.toString()); - - output->append("Log : server listening on port " + QString::number(QChatlisServer::PORT_NB)); - - connect(server, &QChatlisServer::serverLog, output, &QTextEdit::append); - - centralLayout->addWidget(outputGroupBox); - centralWidget->setLayout(centralLayout); - setCentralWidget(centralWidget); - setWindowTitle("Chatlis - Server"); - resize(400, 200); + if (address.protocol() == QHostAddress::NetworkLayerProtocol::IPv4Protocol) + foundIPV4Addresses += ' ' + address.toString() + " |"; + + if (foundIPV4Addresses.size() > 0) + { + foundIPV4Addresses.remove(foundIPV4Addresses.size() - 1, 1); + output->append("Your local ip address(es) : " + foundIPV4Addresses); + output->append("Log : server listening on port " + QString::number(QChatlisServer::PORT_NB)); + } + else + output->append("No local ip address found."); + + return outputGroupBox; } QServerMainWindow::~QServerMainWindow() diff --git a/Server/QServerMainWindow.h b/Server/QServerMainWindow.h index f19f111..4d2072c 100644 --- a/Server/QServerMainWindow.h +++ b/Server/QServerMainWindow.h @@ -2,6 +2,8 @@ #include #include "QChatlisServer.h" +#include +#include class QServerMainWindow : public QMainWindow { @@ -13,4 +15,7 @@ class QServerMainWindow : public QMainWindow private: QChatlisServer* server; + QTextEdit* output; + + QGroupBox* initOutputGroupBox(); };