diff --git a/README.md b/README.md
new file mode 100644
index 0000000..bc46c00
--- /dev/null
+++ b/README.md
@@ -0,0 +1,145 @@
+# LicenseManager — пользовательская документация
+
+## Назначение
+LicenseManager — настольное приложение на Qt5 для ведения базы клиентов и выпуска файлов лицензий на основе аппаратного идентификатора.
+
+## Возможности
+- хранение клиентов в локальной базе SQLite;
+- добавление, редактирование и удаление записей;
+- импорт данных клиента из JSON-файла (drag-and-drop или выбор файла);
+- выдача файла лицензии по выбранной записи;
+- отображение статуса базы и информации о сборке.
+
+## Требования
+- ОС: Linux/Windows/macOS (Qt5);
+- зависимости при сборке: Qt5 (Core, Gui, Widgets, Sql), OpenSSL;
+- для SQLite: системный драйвер Qt `QSQLITE` (например, пакет `libqt5sql5-sqlite` в Debian/Ubuntu).
+
+## Сборка из исходников
+```bash
+cmake -S . -B build
+cmake --build build -j
+./build/LicenseManager
+```
+
+## Установка как .deb (опционально)
+```bash
+./deb/build.sh /path/to/LicenseManager
+sudo dpkg -i licensemanager_*.deb
+```
+
+## Первое использование и база данных
+- Приложение хранит данные в `./db.sqlite` относительно текущей рабочей директории.
+- При первом запуске, если структура базы отсутствует, приложение предложит создать базу.
+- Если нужен фиксированный путь, запускайте приложение из нужной папки или измените `DB_PATH` в коде.
+
+## Интерфейс
+### Таблица клиентов
+Столбцы:
+- Фамилия, Имя, Отчество
+- Email, Телефон
+- Компания, Город
+- Срок лицензии (если пусто — отображается `Permanent`)
+- Комментарий
+
+### Панель инструментов
+- Add client — добавить запись;
+- Edit client — редактировать выбранную запись;
+- Delete client — удалить выбранные записи;
+- Get info — информация о сборке.
+
+### Контекстное меню таблицы
+- Reload table — обновить список;
+- Add/Edit/Delete client — действия с записями;
+- Get license file — сформировать файл лицензии.
+
+### Иконки действий
+| Действие | Иконка | Файл |
+| --- | --- | --- |
+| App icon |
| `deps/icon.png` |
+| Reload table |
| `deps/reload.png` |
+| Add client |
| `deps/add.png` |
+| Edit client |
| `deps/edit.png` |
+| Delete client |
| `deps/delete.png` |
+| Get info |
| `deps/info.png` |
+| Get license file |
| `deps/getLicenseFile.png` |
+| Drop files (автозаполнение) |
| `deps/dropFiles.png` |
+
+### Строка статуса
+Показывает состояние базы (готова/ошибка/в работе).
+
+## Работа с клиентами
+### Добавление
+1. Нажмите `Add client`.
+2. Вкладка `File autofilled`:
+ - перетащите JSON-файл на область или нажмите и выберите файл;
+ - данные будут подставлены автоматически.
+3. Вкладка `Main info`:
+ - заполните поля вручную.
+4. Вкладка `License time`:
+ - включите `License is temporally`, чтобы выбрать дату окончания.
+5. Нажмите `Save`.
+
+Важно: поле `Hardware hash` можно редактировать только при добавлении записи.
+
+### Редактирование
+1. Выберите одну запись.
+2. Нажмите `Edit client`.
+3. Измените данные и нажмите `Save`.
+
+Если нужно изменить `Hardware hash`, удалите запись и создайте новую.
+
+### Удаление
+Выберите записи и нажмите `Delete client` (или через контекстное меню). Подтвердите удаление.
+
+### Обновление списка
+Используйте `Reload table` для перечитывания данных из базы.
+
+## Генерация файла лицензии
+1. Выберите одну запись в таблице.
+2. Нажмите `Get license file`.
+3. Укажите место сохранения (по умолчанию — `Documents/` или `Home/`).
+4. Файл сохраняется с расширением `.dat` (если расширение не указано, оно добавляется автоматически).
+
+Внутри файла содержится зашифрованный (RSA) CBOR/JSON с полями:
+- `machineId` — аппаратный идентификатор (`hardwareHash`);
+- `validUntil` — дата окончания лицензии в ISO-формате (опционально).
+
+## Формат JSON для авто‑заполнения
+Пример:
+```json
+{
+ "lastName": "Иванов",
+ "firstName": "Иван",
+ "patronymic": "Иванович",
+ "email": "ivan@example.com",
+ "phone": "+7(777)777-77-77",
+ "city": "Москва",
+ "yourCompany": "ООО Ромашка",
+ "hardwareHash": "abcdef0123456789"
+}
+```
+Любое поле можно опустить — оно останется пустым. Для выдачи лицензии важно заполнить `hardwareHash`.
+
+## Хранилище данных
+SQLite-файл: `db.sqlite` в рабочей директории.
+
+Таблица `clients`:
+- `id` (INTEGER, PK, AUTOINCREMENT)
+- `city`, `email`, `firstName`, `hardwareHash`, `lastName`, `patronymic`, `phone`, `yourCompany` (TEXT, NOT NULL)
+- `licenseTime` (TEXT, nullable)
+- `comment` (TEXT, nullable)
+
+## Настройки
+Размеры окон сохраняются через `QSettings` (организация `LicenseManager`).
+Расположение конфигурации зависит от ОС (на Linux обычно `~/.config/LicenseManager/`).
+
+## Технический обзор (для сопровождения)
+- Стек: C++20, Qt5 Widgets/Sql, OpenSSL, SQLite.
+- Основные модули:
+ - `MainWidget` — главное окно, таблица, меню, выдача лицензии.
+ - `LicenseModel` — модель таблицы и работа с SQLite (в асинхронных задачах).
+ - `EditClientDialog` — диалог добавления/редактирования, импорт JSON.
+ - `LicenseGenerator`/`Crypt` — утилиты шифрования и генерации файла (часть функций не привязана к UI).
+- Ресурсы Qt (`deps.qrc`) включают иконки, `tables.ddl` для инициализации БД и приватный ключ `Key`.
+- Для смены ключа шифрования замените `deps/Key` и пересоберите проект.
diff --git a/src/EditClientDialog/DropFileWidget.h b/src/EditClientDialog/DropFileWidget.h
index 911bea0..0c20b9e 100644
--- a/src/EditClientDialog/DropFileWidget.h
+++ b/src/EditClientDialog/DropFileWidget.h
@@ -1,22 +1,54 @@
#ifndef LICENSEMANAGER_DROPFILEWIDGET_H
#define LICENSEMANAGER_DROPFILEWIDGET_H
+/**
+ * @file DropFileWidget.h
+ * @brief Виджет для перетаскивания файлов.
+ *
+ * @details
+ * Предоставляет drag-and-drop и выбор файлов через диалог.
+ */
+
// Qt
#include
class QLabel;
+/**
+ * @brief Виджет для приёма файлов через drag-and-drop.
+ */
class DropFileWidget : public QWidget {
Q_OBJECT
public:
+ /**
+ * @brief Создаёт виджет.
+ */
explicit DropFileWidget(QWidget *parent = nullptr);
signals:
+ /**
+ * @brief Сигнал о том, что файлы выбраны или перетащены.
+ */
void filesDropped(const QStringList &paths);
protected:
+ /**
+ * @brief Обрабатывает начало перетаскивания.
+ */
void dragEnterEvent(QDragEnterEvent *e) override;
+ /**
+ * @brief Обрабатывает перемещение в области виджета.
+ */
void dragMoveEvent(QDragMoveEvent *e) override;
+ /**
+ * @brief Обрабатывает сброс файлов.
+ */
void dropEvent(QDropEvent *e) override;
+ /**
+ * @brief Обрабатывает клик для открытия диалога выбора.
+ */
void mousePressEvent(QMouseEvent *e) override;
private:
+ /**
+ * @brief Открывает диалог выбора файлов.
+ */
void selectFiles();
};
diff --git a/src/EditClientDialog/EditClientDialog.h b/src/EditClientDialog/EditClientDialog.h
index 3586f56..33c0b04 100644
--- a/src/EditClientDialog/EditClientDialog.h
+++ b/src/EditClientDialog/EditClientDialog.h
@@ -1,6 +1,15 @@
#ifndef LICENSEMANAGER_EDITCLIENTDIALOG_H
#define LICENSEMANAGER_EDITCLIENTDIALOG_H
+/**
+ * @file EditClientDialog.h
+ * @brief Диалог добавления/редактирования клиента.
+ *
+ * @details
+ * Позволяет заполнять данные вручную или автозаполнением из файла,
+ * а также задавать срок действия лицензии.
+ */
+
// Qt
#include
#include
@@ -18,52 +27,91 @@ class QTextEdit;
// Self
class LicenseModel;
+/**
+ * @brief Диалог редактирования данных клиента.
+ */
class EditClientDialog : public QDialog
{
Q_OBJECT
public:
+ /**
+ * @brief Режим работы диалога.
+ */
enum class Type
{
- None = 0,
- Add,
- Edit,
+ None = 0, //!< Режим не выбран.
+ Add, //!< Добавление нового клиента.
+ Edit, //!< Редактирование существующего клиента.
};
+ /**
+ * @brief Создаёт диалог для заданной модели.
+ */
EditClientDialog(LicenseModel* model, QWidget *parent = nullptr);
+ /**
+ * @brief Деструктор.
+ */
~EditClientDialog();
+ /**
+ * @brief Устанавливает режим диалога.
+ */
void setType(Type type);
+ /**
+ * @brief Возвращает текущий режим диалога.
+ */
Type getType() const;
+ /**
+ * @brief Формирует структуру клиента из полей формы.
+ */
LicenseModel::LicenseItem getLicenseItem() const;
+ /**
+ * @brief Очищает поля ввода.
+ */
void clear();
+ /**
+ * @brief Заполняет поля данными клиента по индексу строки.
+ */
void setClientInfo(int index);
private slots:
+ /**
+ * @brief Обрабатывает выбор/перетаскивание файла с конфигурацией.
+ */
void onFilesChanged(const QStringList &paths);
private:
+ /**
+ * @brief Загружает настройки окна (размер).
+ */
void loadSettings();
+ /**
+ * @brief Сохраняет настройки окна (размер).
+ */
void saveSettings();
+ /**
+ * @brief Создаёт стандартную подпись для поля ввода.
+ */
QLabel* makeLabel(const QString &text);
private:
- LicenseModel* m_model{nullptr};
- QTabWidget* m_tabWidget{nullptr};
- QLabel* m_configPathLabel{nullptr};
- QWidget* m_manualWidget{nullptr};
- QLineEdit* m_firstNameLineEdit{nullptr}; //!< Поле ввода имени пользователя.
- QLineEdit* m_lastNameLineEdit{nullptr}; //!< Поле ввода фамилии пользователя.
- QLineEdit* m_patronymicLineEdit{nullptr}; //!< Поле ввода отчества пользователя.
- QLineEdit* m_emailLineEdit{nullptr}; //!< Поле ввода электронной почты.
- QLineEdit* m_phoneLineEdit{nullptr}; //!< Поле ввода номера телефона.
- QLineEdit* m_yourCompanyNameTextEdit{nullptr}; //!< Поле ввода названия компании пользователя.
- QLineEdit* m_sellerNameTextEdit{nullptr}; //!< Поле ввода названия продавца (если используется).
- QLineEdit* m_cityLineEdit{nullptr}; //!< Поле ввода города.
- QLineEdit* m_hardwareHashLineEdit{nullptr}; //!< Поле для отображения хеша оборудования.
- QCheckBox* m_useTemporaryLicenseCheckBox{nullptr};//!<
- QCalendarWidget* m_calendarWidget{nullptr}; //!<
- QTextEdit* m_commentTextEdit{nullptr}; //!<
- QPushButton* m_saveButton{nullptr}; //!<
- QString m_filesPath;
- Type m_type = Type::None;
+ LicenseModel* m_model{nullptr}; //!< Модель данных клиентов.
+ QTabWidget* m_tabWidget{nullptr}; //!< Набор вкладок диалога.
+ QLabel* m_configPathLabel{nullptr}; //!< Отображение пути к файлу.
+ QWidget* m_manualWidget{nullptr}; //!< Вкладка ручного ввода.
+ QLineEdit* m_firstNameLineEdit{nullptr}; //!< Поле ввода имени пользователя.
+ QLineEdit* m_lastNameLineEdit{nullptr}; //!< Поле ввода фамилии пользователя.
+ QLineEdit* m_patronymicLineEdit{nullptr}; //!< Поле ввода отчества пользователя.
+ QLineEdit* m_emailLineEdit{nullptr}; //!< Поле ввода электронной почты.
+ QLineEdit* m_phoneLineEdit{nullptr}; //!< Поле ввода номера телефона.
+ QLineEdit* m_yourCompanyNameTextEdit{nullptr}; //!< Поле ввода названия компании пользователя.
+ QLineEdit* m_sellerNameTextEdit{nullptr}; //!< Поле ввода названия продавца (если используется).
+ QLineEdit* m_cityLineEdit{nullptr}; //!< Поле ввода города.
+ QLineEdit* m_hardwareHashLineEdit{nullptr}; //!< Поле для отображения хеша оборудования.
+ QCheckBox* m_useTemporaryLicenseCheckBox{nullptr}; //!< Признак временной лицензии.
+ QCalendarWidget* m_calendarWidget{nullptr}; //!< Календарь для выбора даты.
+ QTextEdit* m_commentTextEdit{nullptr}; //!< Поле ввода комментария.
+ QPushButton* m_saveButton{nullptr}; //!< Кнопка сохранения.
+ QString m_filesPath; //!< Последний выбранный файл.
+ Type m_type = Type::None; //!< Текущий режим диалога.
};
#endif //LICENSEMANAGER_EDITCLIENTDIALOG_H
diff --git a/src/LicenseGenerator/LicenseGenerator.cpp b/src/LicenseGenerator/LicenseGenerator.cpp
deleted file mode 100644
index 1751991..0000000
--- a/src/LicenseGenerator/LicenseGenerator.cpp
+++ /dev/null
@@ -1,53 +0,0 @@
-#include "LicenseGenerator.h"
-
-#include "crypt.h"
-#include "Util.h"
-
-LicenseGenerator::Result LicenseGenerator::createEncryptedFile(const QString& outputFile,
- const QString& sshTarget,
- const QString& password,
- const RunSshFn& runSsh)
-{
- Result result;
-
- QByteArray text;
- if (!runSsh("ssh",
- QStringList() << sshTarget << "sed -rn 's/Serial\\t\\t: (.+)/\\1/p' /proc/cpuinfo",
- password,
- "get cpu id",
- &text))
- {
- result.error = Error::SshFailed;
- return result;
- }
-
- result.cpuId = text;
-
- if (text.isEmpty())
- {
- result.error = Error::CpuIdEmpty;
- return result;
- }
-
- if ((text.length() != 16 && text.length() != 20) || findFirstNotOf(text, "0123456789abcdef") >= 0)
- {
- result.error = Error::CpuIdInvalid;
- return result;
- }
-
- QByteArray encrypted = Crypt::rsaEncrypt(text, fileReadAll(":/deps/Key"));
- if (encrypted.isEmpty())
- {
- result.error = Error::EncryptFailed;
- return result;
- }
-
- if (!fileWriteAll(outputFile, encrypted))
- {
- result.error = Error::WriteFailed;
- return result;
- }
-
- result.error = Error::Ok;
- return result;
-}
diff --git a/src/LicenseGenerator/LicenseGenerator.h b/src/LicenseGenerator/LicenseGenerator.h
deleted file mode 100644
index 8d5e52b..0000000
--- a/src/LicenseGenerator/LicenseGenerator.h
+++ /dev/null
@@ -1,31 +0,0 @@
-#pragma once
-
-#include
-#include
-#include
-#include
-
-class LicenseGenerator
-{
-public:
- enum class Error {
- Ok,
- SshFailed,
- CpuIdEmpty,
- CpuIdInvalid,
- EncryptFailed,
- WriteFailed
- };
-
- struct Result {
- Error error = Error::Ok;
- QByteArray cpuId;
- };
-
- using RunSshFn = std::function;
-
- static Result createEncryptedFile(const QString& outputFile,
- const QString& sshTarget,
- const QString& password,
- const RunSshFn& runSsh);
-};
diff --git a/src/LicenseGenerator/Util.cpp b/src/LicenseGenerator/Util.cpp
index dff9bf8..169e7a9 100644
--- a/src/LicenseGenerator/Util.cpp
+++ b/src/LicenseGenerator/Util.cpp
@@ -1,13 +1,8 @@
#include "Util.h"
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
+#include
+#include
+#include
QByteArray fileReadAll(const QString& path)
{
@@ -18,288 +13,6 @@ QByteArray fileReadAll(const QString& path)
return buf;
}
-bool fileWriteAll(const QString& path, const QByteArray& data, int bufOffset)
-{
- QFile f(path);
- if (!f.exists()) {
- QFileInfo fi(f);
- QDir d;
- d.mkpath(fi.dir().path());
- }
- if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate))
- return false;
- int size = data.size() - bufOffset;
- int bytesWritten = f.write(data.constData() + bufOffset, size);
- f.close();
- return bytesWritten == size;
-}
-
-bool splitAddr(const QString& addrOrNetwork, bool isAddress, uint& addr, uint* mask)
-{
- QString s = "^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})";
- if (mask)
- s += "/(\\d\\d?)";
- s += '$';
- QRegularExpression re(s, QRegularExpression::CaseInsensitiveOption);
- QRegularExpressionMatch m = re.match(addrOrNetwork);
- if (!m.hasMatch()) return false;
-
- uint a = m.captured(1).toUInt();
- if (a == 0 || a > 255) return false;
- uint b = m.captured(2).toUInt();
- if (b > 255) return false;
- uint c = m.captured(3).toUInt();
- if (c > 255) return false;
- uint d = m.captured(4).toUInt();
- if ((isAddress && d == 0) || d >= 255) return false;
- if (mask)
- {
- *mask = m.captured(5).toInt();
- if (*mask == 0 || *mask > 30) return false;
- }
- addr = (a << 24) | (b << 16) | (c << 8) | d;
- return true;
-}
-
-bool ipOk(const QString& addr, bool netmask)
-{
- uint a;
- if (netmask)
- {
- uint m;
- return splitAddr(addr, true, a, &m);
- }
- return splitAddr(addr, true, a);
-}
-
-QString makeHash(const QString& password)
-{
- std::random_device rd;
- QString result;
- while(result.length() < 16) // salt
- {
- uint a = rd() % 52;
- result += QChar(a < 26 ? ('a' + a) : ('A' + a - 26));
- }
- QCryptographicHash hash(QCryptographicHash::Sha1);
- hash.addData(result.toLatin1());
- hash.addData(password.toUtf8());
- (result += '$') += hash.result().toHex();
- return result;
-}
-
-bool testPassword(const QByteArray& password, const QByteArray& passwordHash)
-{
- int j = passwordHash.indexOf('$');
- if (j < 0) return false;
- QCryptographicHash hash(QCryptographicHash::Sha1);
- hash.addData(passwordHash.mid(0, j));
- hash.addData(password);
- const QByteArray h = hash.result();
- return std::equal(h.constData(), h.constData() + h.size(), passwordHash.constData() + j + 1);
-}
-
-int findFirstNotOf(const QString& s, const char* chars, int pos, int n)
-{
- if (n >= 0) n += pos;
- else n = s.length();
-
- for(; pos < n; ++pos)
- for(const char* p = chars;; p++)
- {
- if (!*p) return pos;
- if (s[pos] == char16_t(*p)) break;
- }
- return -1;
-}
-
-int findFirstNotOf(const QString& s, QChar c, int pos, int n)
-{
- if (n >= 0) n += pos;
- else n = s.length();
-
- for(; pos < n; ++pos)
- {
- if (s[pos] != c)
- return pos;
- }
- return -1;
-}
-
-QByteArray& trimLeft(QByteArray& s, char c)
-{
- int i = 0, length = s.length();
- for(; i < length; ++i)
- if (s.at(i) != c)
- break;
- s.remove(0, i);
- return s;
-}
-
-QByteArray& trimRight(QByteArray& s, char c)
-{
- if (!s.isEmpty())
- {
- int i = s.length() - 1;
- for(; i >= 0; --i)
- if (s.at(i) != c)
- break;
- i++;
- s.resize(i);
- }
- return s;
-}
-
-QByteArray& trimLeft(QByteArray& s, const char* chars) // ascii only
-{
- int i = 0, length = s.length();
- for(; i < length; ++i)
- {
- QChar c = s.at(i);
- for(const char* p = chars;; p++)
- {
- if (!*p) goto breakfor;
- if (char16_t(*p) == c) break;
- }
- }
-breakfor:
- s.remove(0, i);
- return s;
-}
-
-QByteArray& trimRight(QByteArray& s, const char* chars) // ascii only
-{
- if (!s.isEmpty())
- {
- int i = s.length() - 1;
- for(; i >= 0; --i)
- {
- QChar c = s.at(i);
- for(const char* p = chars;; p++)
- {
- if (!*p) goto breakfor;
- if (char16_t(*p) == c) break;
- }
- }
- breakfor:
- i++;
- s.resize(i);
- }
- return s;
-}
-
-QString& trimLeft(QString& s, QChar c)
-{
- int i = 0, length = s.length();
- for(; i < length; ++i)
- if (s.at(i) != c)
- break;
- s.remove(0, i);
- return s;
-}
-
-QString& trimRight(QString& s, QChar c)
-{
- if (!s.isEmpty())
- {
- int i = s.length() - 1;
- for(; i >= 0; --i)
- if (s.at(i) != c)
- break;
- i++;
- s.resize(i);
- }
- return s;
-}
-
-QString& append(QString& s, const QVector& v, QChar sep)
-{
- if (!v.isEmpty())
- {
- for(int i = 0; i < v.size(); ++i)
- (s += QString::number(v[i])) += sep;
- s.resize(s.size() - 1);
- }
- return s;
-}
-
-QVector splitToInts(const QString& s, QChar sep)
-{
- QVector r;
- if (!s.isEmpty())
- for(const QString& ss : s.split(sep))
- r << ss.toInt();
- return r;
-}
-
-QString getPath(const QString& path)
-{
- int i = path.length();
- if (i == 0 || i == 1) return {};
- --i;
- if (path[i] == '/') --i;
- i = path.lastIndexOf('/', i);
- if (i == 0) return QString(1, '/');
- if (i < 0) return QString();
- return path.mid(0, i);
-}
-
-QString getFileName(const QString& path)
-{
- if (path.isEmpty()) return {};
- int i = path.lastIndexOf('/');
- return i >= 0 ? path.mid(i + 1) : path;
-}
-
-
-QJsonArray convertVectorToJsonArray(const QVector &vector)
-{
- QJsonArray resultArray;
- for (const bool & value: vector){
- resultArray.append(value);
- }
- return resultArray;
-}
-
-QVector convertJsonArrayToVector(const QJsonArray &array)
-{
- QVector vector;
- for (int i = 0; i < array.count(); i++){
- vector.append(array[i].toBool());
- }
- return vector;
-}
-
-
-QByteArray addSizeToByteArray(const QByteArray &arr)
-{
- QByteArray byteArray;
- QDataStream stream(&byteArray, QIODevice::WriteOnly);
- stream << arr.size();
- return byteArray + arr;
-}
-
-int getSizeJsonInByteArr(const QByteArray &arr)
-{
- int size = -1;
- if (arr.size() > 4) {
- QDataStream ds(arr.mid(0, sizeof(int)));
- ds >> size;
- }
- return size;
-}
-
-QString &append(QString &s, const QVector &v, QChar sep)
-{
- if (!v.isEmpty())
- {
- for(int i = 0; i < v.size(); ++i)
- (s += QString(v[i])) += sep;
- s.resize(s.size() - 1);
- }
- return s;
-}
-
QByteArray toCbor(const QJsonObject &jsonObject)
{
QCborValue cborValue = QCborValue::fromJsonValue(QJsonValue(jsonObject));
@@ -321,90 +34,3 @@ QByteArray toCbor(const QJsonDocument &doc)
}
return {}; // Return an empty QByteArray if the document is invalid
}
-
-QJsonDocument fromCbor(const QByteArray &cborData)
-{
- QCborValue cborValue = QCborValue::fromCbor(cborData);
-
- if (cborValue.isMap()) {
- return QJsonDocument(cborValue.toMap().toJsonObject());
- } else if (cborValue.isArray()) {
- return QJsonDocument(cborValue.toArray().toJsonArray());
- }
-
- // Return an empty QJsonDocument if the type is unsupported
- return {};
-}
-
-
-QVector arrayFromMask(uint8_t mask)
-{
- int idx=0;
- QVector ret;
- while (mask) {
- if(mask & 1) ret.append(idx);
- mask>>=1;
- idx++;
- }
- return ret;
-}
-
-
-QVector intersectUnique(const QVector &a, const QVector &b) {
-#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
- QSet sa(a.cbegin(), a.cend());
- QSet sb(b.cbegin(), b.cend());
-#else
- QSet sa = QSet::fromList(a.toList());
- QSet sb = QSet::fromList(b.toList());
-#endif
-
- // In-place intersection; 'sa' becomes the result
- sa.intersect(sb);
-
- QVector out;
- out.reserve(sa.size());
- for (int v : qAsConst(sa)) out.append(v);
-
- // Optional: deterministic order
- std::sort(out.begin(), out.end());
- return out;
-}
-
-int popCount(const QVector &vec, bool value)
-{
- int count = 0;
- for (bool v : vec) {
- if (v == value)
- ++count;
- }
- return count;
-}
-
-QVector vectorOr(const QVector &v1, const QVector &v2)
-{
- Q_ASSERT(v1.size() == v2.size()); // sanity check
- QVector v3(v1.size());
- std::transform(v1.cbegin(), v1.cend(), v2.cbegin(), v3.begin(),
- [](bool x, bool y){ return x || y; });
- return v3;
-}
-
-QVector invert(const QVector &v)
-{
- QVector result(v.size());
- std::transform(v.cbegin(), v.cend(), result.begin(),
- [](bool b){ return !b; });
- return result;
-}
-
-QString toString(const QVector &vec, bool value)
-{
- QStringList out;
- for (int i = 0; i < vec.size(); ++i) {
- if (vec[i] == value) {
- out << QString::number(i + 1); // 1-based index
- }
- }
- return out.join(", ");
-}
diff --git a/src/LicenseGenerator/Util.h b/src/LicenseGenerator/Util.h
index 07d473d..6a0842c 100644
--- a/src/LicenseGenerator/Util.h
+++ b/src/LicenseGenerator/Util.h
@@ -1,304 +1,29 @@
#pragma once
-#include
-#include
-#include
-#include
-#include
-#include
+/**
+ * @file Util.h
+ * @brief Базовые вспомогательные функции проекта.
+ */
+
+#include
#include
+#include
#include
-#include
-#include
-#include
-#include
-
-template
-class FifoContainer {
-
-public:
- explicit FifoContainer(std::size_t maxSize) : m_maxSize(maxSize) {}
- void add(T value)
- {
- if (m_container.size() >= m_maxSize)
- m_container.pop_front(); // Remove the oldest element (FIFO)
- m_container.push_back(value); // Add the new element
- }
- std::optional getAverage() const
- {
- if (m_container.size() < m_maxSize) {
- return std::nullopt; // Indicate that the container is not yet full
- }
- // Use std::accumulate to calculate the sum of the elements
- T sum = std::accumulate(m_container.begin(), m_container.end(), T(0));
- return static_cast(sum) / m_container.size();
- }
- void clear() { m_container.clear(); }
- std::deque &container() { return m_container; }
- operator QString() const {
- QStringList l;
- for (int i = 0; i < m_container.size(); ++i) {
- l << QString::number(m_container[i]);
- }
- return l.join(", ");
- }
-
-private:
- std::size_t m_maxSize; // Maximum size of the m_container
- std::deque m_container; // FIFO container
-};
+#include
+/**
+ * @brief Считывает файл целиком.
+ */
QByteArray fileReadAll(const QString& path);
-inline QStringList fileReadAllLines(const QString& path)
-{
-#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
- return QString(fileReadAll(path)).split('\n', Qt::SkipEmptyParts);
-#else
- return QString(fileReadAll(path)).split('\n', QString::SkipEmptyParts);
-#endif
-}
-
-bool fileWriteAll(const QString& path, const QByteArray& data, int bufOffset = 0);
-inline bool fileWriteAllLines(const QString& path, const QStringList& list) { return fileWriteAll(path, list.join('\n').toUtf8()); }
-
-template >
-class Ptr : public QScopedPointer
-{
- typedef QScopedPointer base;
-public:
- using base::base;
- operator T*() const { return base::data(); }
- operator bool() const { return base::data() != 0; }
-#if QT_VERSION < QT_VERSION_CHECK(5, 11, 0)
- T* get() const { return base::data(); }
-#endif
-};
-
-//inline bool isConnectingOrConnected(const QTcpSocket* s)
-//{ return s->state() == QAbstractSocket::ConnectingState || s->state() == QAbstractSocket::ConnectedState; }
-inline bool isConnected(const QTcpSocket* s) { return s->state() == QAbstractSocket::ConnectedState; }
-
-QString getPath(const QString& path);
-QString getFileName(const QString& path);
-
-QString toStr(const QHostAddress& a);
-bool splitAddr(const QString& addrOrNetwork, bool isAddress, uint& addr, uint* mask = 0);
-bool ipOk(const QString& addr, bool netmask);
-
-template
-class Vector : public std::vector
-{
- typedef std::vector base;
-public:
- using base::base;
- int size() const { return base::size(); }
- void clear() { base::clear(); base::shrink_to_fit(); }
-
- void remove(int pos) { /*checkIndex(pos);*/ base::erase(base::begin() + pos); }
-};
-
-template
-class List : public std::list
-{
- typedef std::list base;
-public:
- using base::base;
- int size() const { return base::size(); }
-
- List& operator+=(const T& v) { base::push_back(v); return *this; }
- List& operator+=(const List& v) { base::insert(base::end(), v.begin(), v.end()); return *this; }
- List& operator<<(const T& v) { return *this += v; }
- List& operator<<(const List& v) { return *this += v; }
-};
-
-namespace Alg
-{
-template inline void remove(C& c, const typename C::value_type& value)
-{
- typename C::iterator i = std::remove(c.begin(), c.end(), value);
- c.erase(i, c.end());
-}
-template inline void removeIf(C& c, P pred)
-{
- typename C::iterator i = std::remove_if(c.begin(), c.end(), pred);
- c.erase(i, c.end());
-}
-
-template inline bool contains(I first, I last, const T& value)
-{
- I i = std::find(first, last, value);
- return i != last;
-}
-template inline bool containsIf(I first, I last, P pred)
-{
- I i = std::find_if(first, last, pred);
- return i != last;
-}
-
-template inline C& sort(C& c) { std::sort(c.begin(), c.end()); return c; }
-template inline C& sort(C& c, const P& pred) { std::sort(c.begin(), c.end(), pred); return c; }
-
-template inline bool contains(const C& c, const typename C::value_type& value) { return contains(c.begin(), c.end(), value); }
-template inline bool containsIf(const C& c, P pred) { return containsIf(c.begin(), c.end(), pred); }
-
-template inline typename C::iterator find(C& c, const T& v) { return std::find(c.begin(), c.end(), v); }
-template inline typename C::const_iterator find(const C& c, const T& v) { return std::find(c.begin(), c.end(), v); }
-
-template inline typename C::iterator findIf(C& c, const P& pred) { return std::find_if(c.begin(), c.end(), pred); }
-template inline typename C::const_iterator findIf(const C& c, const P& pred) { return std::find_if(c.begin(), c.end(), pred); }
-template inline int findIndex(const C& c, const typename C::value_type& v)
-{
- for(int i = 0; i < int(c.size()); ++i)
- if (v == c[i])
- return i;
- return -1;
-}
-template inline int findIndexIf(const C& c, const P& pred)
-{
- for(int i = 0; i < int(c.size()); ++i)
- if (pred(c[i]))
- return i;
- return -1;
-}
-template inline int rfindIndexIf(const C& c, const P& pred)
-{
- for(int i = int(c.size()) - 1; i >= 0; --i)
- if (pred(c[i]))
- return i;
- return -1;
-}
-
-template C& forEach(C& c, F f) { std::for_each(c.begin(), c.end(), f); return c; }
-template const C& forEach(const C& c, F f) { std::for_each(c.begin(), c.end(), f); return c; }
-template inline void fill(C& c, const T& value) { std::fill(c.begin(), c.end(), value); }
-
-template It copy(C& c, It to) { return std::copy(c.begin(), c.end(), to); }
-
-//template C select(const C& v, P pred)
-
-template QVector selectIndexes(const QVector& v, P pred)
-{
- QVector r;
- for(int i = 0; i < int(v.size()); ++i)
- if (pred(v[i]))
- r << i;
- return r;
-}
-
-template inline int countIf(const C &c, const P &pred) { return std::count_if(c.begin(), c.end(), pred); }
-}
-
-// Returns pointer to value if the variant holds T, otherwise nullptr
-template
-inline decltype(auto) get_if_type(Variant& v) {
- return std::holds_alternative(v) ? std::addressof(std::get(v)) : nullptr;
-}
-
-template
-inline const T* get_if_polymorphic(const Variant& v) {
- if (const auto* p = dynamic_cast(std::addressof(v))) {
- return p;
- }
- return nullptr;
-}
-
-const QString DateFormat = "dd.MM.yyyy", DtFormat = "dd.MM.yyyy HH:mm", DtFormatSec = "dd.MM.yyyy HH:mm:ss", DtFormatSecMsec = "ss:zzz";
-
-bool isRu();
-
-//inline QDate nowd() { return QDate::currentDate(); }
-inline QTime nowt() { return QTime::currentTime(); }
-inline QDateTime now() { return QDateTime::currentDateTime(); }
-//inline QDateTime nowutc() { return QDateTime::currentDateTimeUtc(); }
-//inline QDateTime today() { return QDateTime(nowd()); }
-
-inline QString nowstr() { return now().toString(DtFormatSec); }
-//inline QString nowdstr() { return nowd().toString(DateFormat); }
-inline QString nowtstr() { return nowt().toString("hh:mm:ss"); }
-
-QString makeHash(const QString& password);
-bool testPassword(const QByteArray& password, const QByteArray& passwordHash);
-
-//inline QDebug qs() { return qDebug().nospace(); }
-//inline QDebug qq() { return qDebug().noquote(); }
-
-//QByteArray calcHash(const QStringList& list);
-
-inline int compareNoCase(const QString& a, const QString& b) { return a.compare(b, Qt::CaseInsensitive); }
-
-int findFirstNotOf(const QString& s, const char* chars, int pos = 0, int n = -1);
-int findFirstNotOf(const QString& s, QChar c, int pos = 0, int n = -1);
-
-inline void noCaseSort(QStringList& list)
-{ std::sort(list.begin(), list.end(), [](const QString& a, const QString& b) { int r = compareNoCase(a, b); return r == 0 ? (a < b) : r < 0; }); }
-
-QByteArray& trimLeft(QByteArray& s, char c);
-QByteArray& trimRight(QByteArray& s, char c);
-inline QByteArray& trim(QByteArray& s, char c) { return trimLeft(trimRight(s, c), c); }
-
-QByteArray& trimLeft(QByteArray& s, const char* chars); // ascii only
-QByteArray& trimRight(QByteArray& s, const char* chars); // ascii only
-inline QByteArray& trim(QByteArray& s, const char* chars) { return trimLeft(trimRight(s, chars), chars); }
-
-QString& trimLeft(QString& s, QChar c);
-QString& trimRight(QString& s, QChar c);
-inline QString& trim(QString& s, QChar c) { return trimLeft(trimRight(s, c), c); }
-
-//QString& trimLeft(QString& s, const char* chars); // ascii only
-//QString& trimRight(QString& s, const char* chars); // ascii only
-//inline QString& trim(QString& s, const char* chars) { return trimLeft(trimRight(s, chars), chars); }
-
-//QString& trimLeft(QString& s, const char16_t* chars);
-//QString& trimRight(QString& s, const char16_t* chars);
-//inline QString& trim(QString& s, const char* chars) { return trimLeft(trimRight(s, chars), chars); }
-
-QString& append(QString& s, const QVector& v, QChar sep);
-inline QString join(const QVector& v, QChar sep)
-{
- QString s;
- append(s, v, sep);
- return s;
-}
-QString &append(QString &s, const QVector &v, QChar sep);
-QVector splitToInts(const QString& s, QChar sep);
-
-QVector getIpAddr();
-
-QVector convertStringToVectorBool(const QString &string);
-QByteArray addSizeToByteArray(const QByteArray &arr);
-int getSizeJsonInByteArr(const QByteArray &arr);
-QString toString(const QVector &vec, bool value);
-//Counts values in bool vectors
-int popCount(const QVector &vec, bool value);
-QVector vectorOr(const QVector &a, const QVector &b);
-QVector invert(const QVector &v);
-
-QString execAndReadAll(const QString& prog, const QStringList& args, const QString& workDir, int waitTimeout = 0, bool ignoreErrorOut = false);
-inline QString execAndReadAll1(const QString& prog, const QString& arg, int waitTimeout = 0, bool ignoreErrorOut = false) { return execAndReadAll(prog, QStringList(arg), {}, waitTimeout, ignoreErrorOut); }
-
-QRegularExpressionMatch paCardMatchSinks();
-QRegularExpressionMatch paPciMatchSinks();
-QByteArray getPaUsbOutput();
-QByteArray getPaUsbInput();
-bool isUsbAudioCardPresent();
-QByteArray getDefaultAudioInput();
-QByteArray getDefaultAudioOutput();
-QByteArray getDefaultAudioInputStr();
-QByteArray getDefaultAudioOutputStr();
-
-QByteArray getMachineId();
-
-inline QString qStrFromBA(const QByteArray& chaddr) { return QString::fromUtf8(chaddr.left(6).toHex(':')); }
-#ifndef Q_OS_WINDOWS
-bool getVersion();
-#endif
-
-QJsonArray convertVectorToJsonArray(const QVector &vector);
-QVector convertJsonArrayToVector(const QJsonArray &array);
+/**
+ * @brief Конвертирует QJsonObject в CBOR.
+ */
QByteArray toCbor(const QJsonObject &jsonObject);
+/**
+ * @brief Конвертирует QJsonArray в CBOR.
+ */
QByteArray toCbor(const QJsonArray &jsonArray);
+/**
+ * @brief Конвертирует QJsonDocument в CBOR.
+ */
QByteArray toCbor(const QJsonDocument &doc);
-QJsonDocument fromCbor(const QByteArray &cborData);
-
-QVector arrayFromMask(uint8_t mask);
-QVector intersectUnique(const QVector& a, const QVector& b);
diff --git a/src/LicenseGenerator/crypt.cpp b/src/LicenseGenerator/crypt.cpp
index 909dc2e..36c2015 100644
--- a/src/LicenseGenerator/crypt.cpp
+++ b/src/LicenseGenerator/crypt.cpp
@@ -1,365 +1,11 @@
#include "crypt.h"
+
#include
#include
#include
#include
#include
-#include
#include
-#include
-
-QPair Crypt::generateRSAKeyPair() {
- QPair keyPair;
- EVP_PKEY *keypair = nullptr;
- EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr);
-
- if (!ctx || EVP_PKEY_keygen_init(ctx) <= 0 || EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 2048) <= 0) {
- qDebug() << "Failed to initialize RSA key generation context";
- EVP_PKEY_CTX_free(ctx);
- return keyPair;
- }
-
- if (EVP_PKEY_keygen(ctx, &keypair) <= 0) {
- qDebug() << "Failed to generate RSA key pair";
- EVP_PKEY_CTX_free(ctx);
- return keyPair;
- }
-
- // Convert private key to DER format
- BIO *privateBio = BIO_new(BIO_s_mem());
- if (i2d_PrivateKey_bio(privateBio, keypair) <= 0) {
- qDebug() << "Failed to write private key in DER format";
- EVP_PKEY_free(keypair);
- BIO_free(privateBio);
- EVP_PKEY_CTX_free(ctx);
- return keyPair;
- }
- char *privateData = nullptr;
- long privateLen = BIO_get_mem_data(privateBio, &privateData);
- keyPair.first = QByteArray(privateData, privateLen);
- BIO_free(privateBio);
-
- // Convert public key to DER format
- BIO *publicBio = BIO_new(BIO_s_mem());
- if (i2d_PUBKEY_bio(publicBio, keypair) <= 0) {
- qDebug() << "Failed to write public key in DER format";
- EVP_PKEY_free(keypair);
- BIO_free(publicBio);
- EVP_PKEY_CTX_free(ctx);
- return keyPair;
- }
- char *publicData = nullptr;
- long publicLen = BIO_get_mem_data(publicBio, &publicData);
- keyPair.second = QByteArray(publicData, publicLen);
- BIO_free(publicBio);
-
- EVP_PKEY_free(keypair);
- EVP_PKEY_CTX_free(ctx);
- return keyPair;
-}
-
-QByteArray Crypt::generateAESKey() {
- QByteArray aesKey(32, 0); // 32 bytes for AES-256
- if (!RAND_bytes(reinterpret_cast(aesKey.data()), aesKey.size())) {
- qDebug() << "Failed to generate AES key";
- }
- return aesKey;
-}
-
-bool Crypt::encryptAESKeyWithRSA(EVP_PKEY *publicKey, const unsigned char *aesKey, size_t aesKeyLength, QByteArray &encryptedKey) {
- encryptedKey.resize(256); // For RSA 2048-bit
- size_t encryptedKeyLength = encryptedKey.size();
-
- EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(publicKey, nullptr);
- if (!ctx || EVP_PKEY_encrypt_init(ctx) <= 0) {
- qDebug() << "Failed to initialize RSA encryption";
- EVP_PKEY_CTX_free(ctx);
- return false;
- }
-
- EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING);
- if (EVP_PKEY_encrypt(ctx, reinterpret_cast(encryptedKey.data()), &encryptedKeyLength, aesKey, aesKeyLength) <= 0) {
- qDebug() << "RSA encryption failed";
- EVP_PKEY_CTX_free(ctx);
- ERR_print_errors_fp(stderr);
- return false;
- }
- encryptedKey.resize(encryptedKeyLength);
- EVP_PKEY_CTX_free(ctx);
- return true;
-}
-
-Crypt::EncryptedData Crypt::encrypt(const QByteArray &plaintext, const QByteArray &publicKey, const QByteArray &aesKey) {
- EncryptedData result;
-
- EVP_PKEY *evpPublicKey = loadKeyFromDER(publicKey, true);
- if (!evpPublicKey) {
- return result;
- }
-
- // Generate random IV
- result.iv.resize(12); // GCM standard recommends a 12-byte IV
- if (!RAND_bytes(reinterpret_cast(result.iv.data()), result.iv.size())) {
- qDebug() << "Failed to generate random IV";
- EVP_PKEY_free(evpPublicKey);
- return result;
- }
-
- // Encrypt the AES key with RSA public key
- if (!encryptAESKeyWithRSA(evpPublicKey, reinterpret_cast(aesKey.data()), aesKey.size(), result.encryptedKey)) {
- qDebug() << "Failed to encrypt AES key with RSA";
- EVP_PKEY_free(evpPublicKey);
- return result;
- }
- EVP_PKEY_free(evpPublicKey); // Free the EVP_PKEY structure for the public key
-
- // AES-GCM encryption of the plaintext
- EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
- if (!ctx) {
- qDebug() << "Failed to create encryption context";
- return result;
- }
-
- if (EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), nullptr, nullptr, nullptr) <= 0 ||
- EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, result.iv.size(), nullptr) <= 0 ||
- EVP_EncryptInit_ex(ctx, nullptr, nullptr, reinterpret_cast(aesKey.data()), reinterpret_cast(result.iv.data())) <= 0) {
- qDebug() << "Failed to initialize AES-GCM encryption";
- EVP_CIPHER_CTX_free(ctx);
- return result;
- }
-
- // Encrypt the plaintext
- QByteArray ciphertext(plaintext.size() + EVP_CIPHER_block_size(EVP_aes_256_gcm()), 0);
- int len = 0, ciphertext_len = 0;
- if (EVP_EncryptUpdate(ctx, reinterpret_cast(ciphertext.data()), &len, reinterpret_cast(plaintext.data()), plaintext.size()) <= 0) {
- qDebug() << "AES-GCM encryption failed at Update step";
- EVP_CIPHER_CTX_free(ctx);
- return result;
- }
- ciphertext_len += len;
-
- // Finalize encryption
- if (EVP_EncryptFinal_ex(ctx, reinterpret_cast(ciphertext.data()) + len, &len) <= 0) {
- qDebug() << "AES-GCM encryption failed at Final step";
- EVP_CIPHER_CTX_free(ctx);
- return result;
- }
- ciphertext_len += len;
- ciphertext.resize(ciphertext_len);
- result.encryptedData = ciphertext;
-
- // Get the GCM authentication tag
- result.tag.resize(16); // Standard GCM tag length is 16 bytes
- if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, result.tag.size(), result.tag.data()) <= 0) {
- qDebug() << "Failed to retrieve GCM tag";
- EVP_CIPHER_CTX_free(ctx);
- return result;
- }
-
- EVP_CIPHER_CTX_free(ctx);
- return result;
-}
-
-bool Crypt::decryptAESKeyWithRSA(EVP_PKEY *privateKey, const QByteArray &encryptedKey, unsigned char *aesKey, size_t aesKeyLength) {
- // Ensure the buffer size is set to the RSA key size (e.g., 256 bytes for a 2048-bit RSA key)
- unsigned char decryptedBuffer[256]; // Adjust size according to RSA key length, e.g., 256 bytes for RSA 2048-bit
- size_t decryptedBufferSize = sizeof(decryptedBuffer);
-
- EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(privateKey, nullptr);
- if (!ctx || EVP_PKEY_decrypt_init(ctx) <= 0) {
- qDebug() << "Failed to initialize RSA decryption";
- EVP_PKEY_CTX_free(ctx);
- return false;
- }
-
- // Set RSA padding to OAEP, which is recommended for encrypting small data such as AES keys
- if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0) {
- qDebug() << "Failed to set RSA padding";
- EVP_PKEY_CTX_free(ctx);
- return false;
- }
-
- // Perform RSA decryption into the `decryptedBuffer`
- if (EVP_PKEY_decrypt(ctx, decryptedBuffer, &decryptedBufferSize, reinterpret_cast(encryptedKey.data()), encryptedKey.size()) <= 0) {
- qDebug() << "RSA decryption failed";
- EVP_PKEY_CTX_free(ctx);
- ERR_print_errors_fp(stderr); // Print detailed OpenSSL error messages
- return false;
- }
-
- // Check if the decrypted data fits the expected AES key size
- if (decryptedBufferSize < aesKeyLength) {
- qDebug() << "Decrypted data is too small for AES key";
- EVP_PKEY_CTX_free(ctx);
- return false;
- }
-
- // Copy only the first `aesKeyLength` bytes to `aesKey`
- memcpy(aesKey, decryptedBuffer, aesKeyLength);
- EVP_PKEY_CTX_free(ctx);
- return true;
-}
-
-QByteArray Crypt::decrypt(const EncryptedData &encryptedData, const QByteArray &privateKey) {
- QByteArray decryptedText;
-
- EVP_PKEY *evpPrivateKey = loadKeyFromDER(privateKey, false);
- if (!evpPrivateKey) {
- return QByteArray();
- }
-
- // Decrypt the AES key using RSA private key
- unsigned char aesKey[32]; // Buffer to hold the 32-byte AES key
- if (!decryptAESKeyWithRSA(evpPrivateKey, encryptedData.encryptedKey, aesKey, sizeof(aesKey))) {
- qDebug() << "Failed to decrypt AES key with RSA";
- EVP_PKEY_free(evpPrivateKey);
- return QByteArray();
- }
- EVP_PKEY_free(evpPrivateKey); // Free the EVP_PKEY structure for the private key
-
- // AES-GCM decryption
- EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
- if (!ctx) {
- qDebug() << "Failed to create decryption context";
- return QByteArray();
- }
-
- // Initialize decryption with AES-256-GCM
- if (EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), nullptr, nullptr, nullptr) <= 0 ||
- EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, encryptedData.iv.size(), nullptr) <= 0 ||
- EVP_DecryptInit_ex(ctx, nullptr, nullptr, aesKey, reinterpret_cast(encryptedData.iv.data())) <= 0) {
- qDebug() << "Failed to initialize AES-GCM decryption";
- EVP_CIPHER_CTX_free(ctx);
- return QByteArray();
- }
-
- // Decrypt the encrypted data
- decryptedText.resize(encryptedData.encryptedData.size());
- int len = 0, plaintext_len = 0;
- if (EVP_DecryptUpdate(ctx, reinterpret_cast(decryptedText.data()), &len,
- reinterpret_cast(encryptedData.encryptedData.data()), encryptedData.encryptedData.size()) <= 0) {
- qDebug() << "AES-GCM decryption failed at Update step";
- EVP_CIPHER_CTX_free(ctx);
- return QByteArray();
- }
- plaintext_len += len;
-
- // Set the expected tag for verification
- if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, encryptedData.tag.size(),
- const_cast(reinterpret_cast(encryptedData.tag.data()))) <= 0) {
- qDebug() << "Failed to set GCM tag for verification";
- EVP_CIPHER_CTX_free(ctx);
- return QByteArray();
- }
-
- // Finalize decryption and verify tag
- if (EVP_DecryptFinal_ex(ctx, reinterpret_cast(decryptedText.data()) + plaintext_len, &len) <= 0) {
- qDebug() << "Tag verification failed or decryption unsuccessful";
- EVP_CIPHER_CTX_free(ctx);
- return QByteArray();
- }
- plaintext_len += len;
-
- EVP_CIPHER_CTX_free(ctx);
- decryptedText.resize(plaintext_len); // Adjust the QByteArray to the actual plaintext length
-
- return decryptedText;
-}
-
-QByteArray Crypt::decrypt(const QByteArray &encryptedData, const QByteArray &privateKey)
-{
- EncryptedData ed;
- QDataStream ds(encryptedData);
- ds >> ed;
- return decrypt(ed, privateKey);
-}
-
-bool Crypt::isValidPublicKeyDER(const QByteArray &derKeyData)
-{
- // Create a memory BIO from the QByteArray
- BIO *bio = BIO_new_mem_buf(derKeyData.constData(), derKeyData.size());
- if (!bio) {
- qWarning("Failed to create BIO");
- return false;
- }
-
- // Attempt to decode the DER-encoded data as a public key
- EVP_PKEY *pkey = d2i_PUBKEY_bio(bio, nullptr);
- BIO_free(bio); // Free the BIO after use
-
- bool isValid = (pkey != nullptr); // If pkey is non-null, it’s a valid key
- if (pkey) {
- EVP_PKEY_free(pkey); // Free the EVP_PKEY structure if it was valid
- }
-
- return isValid;
-}
-
-QByteArray Crypt::rsaDecrypt(const QByteArray &encryptedData, const QByteArray &publicKey)
-{
- EVP_PKEY *pkey = loadKeyFromDER(publicKey, true);
- if (!pkey) {
- return QByteArray();
- }
- EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(pkey, nullptr);
- if (!ctx) {
- EVP_PKEY_free(pkey);
- qDebug() << "Failed to create EVP_PKEY_CTX for decryption.";
- ERR_print_errors_fp(stderr);
- return QByteArray();
- }
-
- const int key_bits =
-#if OPENSSL_VERSION_NUMBER < 0x30000000L
- EVP_PKEY_bits(pkey);
-#else
- EVP_PKEY_get_bits(pkey);
-#endif
- qDebug() << "Key bits" << key_bits;
- EVP_PKEY_free(pkey);
- int chunkSize = key_bits / 8;
-
- if (EVP_PKEY_verify_recover_init(ctx) <= 0) {
- qDebug() << "Failed to initialize verification context.";
- ERR_print_errors_fp(stderr);
- EVP_PKEY_CTX_free(ctx);
- return QByteArray();
- }
-
- if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0) {
- qDebug() << "Failed to set RSA padding.";
- ERR_print_errors_fp(stderr);
- EVP_PKEY_CTX_free(ctx);
- return QByteArray();
- }
-
- QByteArray ret;
- for (int i = 0; i < encryptedData.size() / chunkSize; ++i) {
- int offset = i * chunkSize;
- QByteArray chunk = encryptedData.mid(offset, chunkSize);
- size_t out_len = 0;
- if (EVP_PKEY_verify_recover(ctx, nullptr, &out_len, reinterpret_cast(chunk.data()), chunk.size()) <= 0) {
- qDebug() << "Failed to determine decrypted data size.";
- ERR_print_errors_fp(stderr);
- EVP_PKEY_CTX_free(ctx);
- return QByteArray();
- }
-
- QByteArray decrypted;
- decrypted.resize(out_len);
- if (EVP_PKEY_verify_recover(ctx, reinterpret_cast(decrypted.data()), &out_len, reinterpret_cast(chunk.data()), chunk.size()) <= 0) {
- qDebug() << "Verification (decryption) failed.";
- ERR_print_errors_fp(stderr);
- EVP_PKEY_CTX_free(ctx);
- return QByteArray();
- }
- decrypted.resize(out_len);
- ret.append(decrypted);
- }
-
- EVP_PKEY_CTX_free(ctx);
- return ret;
-}
QByteArray Crypt::rsaEncrypt(const QByteArray &data, const QByteArray &privateKey)
{
@@ -428,17 +74,6 @@ QByteArray Crypt::rsaEncrypt(const QByteArray &data, const QByteArray &privateKe
return ret;
}
-QByteArray Crypt::encryptToByteArray(const QByteArray &plaintext, const QByteArray &publicKey, const QByteArray &aesKey)
-{
- EncryptedData ed = encrypt(plaintext, publicKey, aesKey);
- if (ed.isEmpty())
- return QByteArray();
- QByteArray ret;
- QDataStream ds(&ret, QIODevice::WriteOnly);
- ds << ed;
- return ret;
-}
-
EVP_PKEY *Crypt::loadKeyFromDER(const QByteArray &derKey, bool isPublicKey) {
// Create a memory BIO from the QByteArray
BIO *bio = BIO_new_mem_buf(derKey.constData(), derKey.size());
@@ -458,21 +93,3 @@ EVP_PKEY *Crypt::loadKeyFromDER(const QByteArray &derKey, bool isPublicKey) {
return key;
}
-
-QDataStream &operator<< (QDataStream &ds, const Crypt::EncryptedData &data)
-{
- ds << data.encryptedData;
- ds << data.iv;
- ds << data.encryptedKey;
- ds << data.tag;
- return ds;
-}
-
-QDataStream &operator>> (QDataStream &ds, Crypt::EncryptedData &data)
-{
- ds >> data.encryptedData;
- ds >> data.iv;
- ds >> data.encryptedKey;
- ds >> data.tag;
- return ds;
-}
diff --git a/src/LicenseGenerator/crypt.h b/src/LicenseGenerator/crypt.h
index 8333123..1ac11a0 100644
--- a/src/LicenseGenerator/crypt.h
+++ b/src/LicenseGenerator/crypt.h
@@ -1,41 +1,27 @@
#ifndef CRYPT_H
#define CRYPT_H
+/**
+ * @file crypt.h
+ * @brief Криптографические операции (RSA) для генерации лицензий.
+ *
+ * @details
+ * Оставлены только операции, используемые в проекте.
+ */
+
#include
#include
-#include
class Crypt
{
- static bool encryptAESKeyWithRSA(EVP_PKEY *publicKey, const unsigned char *aesKey, size_t aesKeyLength, QByteArray &encryptedKey);
- static bool decryptAESKeyWithRSA(EVP_PKEY *privateKey, const QByteArray &encryptedKey, unsigned char *aesKey, size_t aesKeyLength);
static EVP_PKEY *loadKeyFromDER(const QByteArray &derPrivateKey, bool isPublicKey);
public:
-
- struct EncryptedData {
- QByteArray encryptedData;
- QByteArray iv;
- QByteArray encryptedKey;
- QByteArray tag;
-
- int size() { return encryptedData.size() + iv.size() + encryptedKey.size() + tag.size(); }
- bool isEmpty() { return encryptedData.isEmpty(); }
- };
-
- static QPair generateRSAKeyPair();
- static QByteArray generateAESKey();
- static EncryptedData encrypt(const QByteArray &plaintext, const QByteArray &publicKey, const QByteArray &aesKey);
- static QByteArray encryptToByteArray(const QByteArray &plaintext, const QByteArray &publicKey, const QByteArray &aesKey);
- static QByteArray decrypt(const EncryptedData &encryptedData, const QByteArray &privateKey);
- static QByteArray decrypt(const QByteArray &encryptedData, const QByteArray &privateKey);
- static bool isValidPublicKeyDER(const QByteArray& derKeyData);
- static QByteArray rsaDecrypt(const QByteArray &encryptedData, const QByteArray &publicKey);
+ /**
+ * @brief RSA-шифрование приватным ключом (sign).
+ */
static QByteArray rsaEncrypt(const QByteArray &data, const QByteArray &privateKey);
};
-QDataStream &operator<< (QDataStream &ds, const Crypt::EncryptedData &data);
-QDataStream &operator>> (QDataStream &ds, Crypt::EncryptedData &data);
-
#endif // CRYPT_H
diff --git a/src/LicenseModel/LicenseModel.h b/src/LicenseModel/LicenseModel.h
index b48fc23..c79637a 100644
--- a/src/LicenseModel/LicenseModel.h
+++ b/src/LicenseModel/LicenseModel.h
@@ -1,73 +1,149 @@
#ifndef LICENSEMANAGER_LICENSEMODEL_H
#define LICENSEMANAGER_LICENSEMODEL_H
+/**
+ * @file LicenseModel.h
+ * @brief Модель данных клиентов и операции с базой SQLite.
+ *
+ * @details
+ * Класс инкапсулирует структуру таблицы клиентов и обеспечивает
+ * асинхронные операции чтения/добавления/редактирования/удаления.
+ */
+
// Qt
#include
#include
#include
+/**
+ * @brief Табличная модель клиентов и лицензий.
+ *
+ * @details
+ * Хранит данные в памяти, синхронизируясь с SQLite. Все операции,
+ * требующие доступа к БД, выполняются в отдельных потоках.
+ */
class LicenseModel : public QAbstractItemModel
{
Q_OBJECT
public:
+ /**
+ * @brief Состояние модели и базы данных.
+ */
enum class Status
{
- None = 0,
- Ok,
- DbStructError,
- DbExistError,
- Working,
+ None = 0, //!< Состояние не определено.
+ Ok, //!< База данных готова.
+ DbStructError, //!< Ошибка структуры или запроса к БД.
+ DbExistError, //!< Ошибка открытия базы данных.
+ Working, //!< Выполняется операция с БД.
};
+ /**
+ * @brief Запись клиента в базе.
+ */
struct LicenseItem
{
- QString id;
- QString city;
- QString email;
- QString firstName;
- QString hardwareHash;
- QString lastName;
- QString patronymic;
- QString phone;
- QString yourCompany;
- QString licenseTime;
- QString comment;
+ QString id; //!< Идентификатор строки в БД.
+ QString city; //!< Город.
+ QString email; //!< Электронная почта.
+ QString firstName; //!< Имя.
+ QString hardwareHash; //!< Аппаратный хеш (machine id).
+ QString lastName; //!< Фамилия.
+ QString patronymic; //!< Отчество.
+ QString phone; //!< Телефон.
+ QString yourCompany; //!< Название компании.
+ QString licenseTime; //!< Срок лицензии (пусто = бессрочно).
+ QString comment; //!< Комментарий.
};
+ /**
+ * @brief Результат асинхронной операции с БД.
+ */
struct Result
{
- QList data;
- QStringList ids;
- Status status = Status::Ok;
- QString error;
+ QList data; //!< Данные, полученные из БД.
+ QStringList ids; //!< Список id, над которыми выполнялась операция.
+ Status status = Status::Ok; //!< Итоговый статус операции.
+ QString error; //!< Текст ошибки (если есть).
};
+ /**
+ * @brief Создаёт модель и выполняет первичную проверку БД.
+ */
explicit LicenseModel(QObject* parent = nullptr);
+ /**
+ * @brief Возвращает количество строк модели.
+ */
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ /**
+ * @brief Возвращает количество столбцов модели.
+ */
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
+ /**
+ * @brief Возвращает отображаемые данные для ячейки.
+ */
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+ /**
+ * @brief Возвращает заголовки строк/столбцов.
+ */
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
+ /**
+ * @brief Табличная модель не имеет иерархии.
+ */
QModelIndex parent(const QModelIndex &index) const override;
+ /**
+ * @brief Возвращает индекс ячейки по координатам.
+ */
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
+ /**
+ * @brief Возвращает текущий статус модели.
+ */
Status getStatus();
+ /**
+ * @brief Возвращает текст ошибок, накопленных моделью.
+ */
QString getStatusText();
+ /**
+ * @brief Создаёт таблицы БД из ресурса `tables.ddl`.
+ */
bool prepareDatabase();
+ /**
+ * @brief Добавляет клиента в БД и обновляет модель.
+ */
void addClient(const LicenseItem &item);
+ /**
+ * @brief Удаляет клиентов по индексам строк.
+ */
void deleteClient(const QList &rows);
+ /**
+ * @brief Обновляет клиента по индексу строки.
+ */
void editClient(const LicenseItem &item, int index);
+ /**
+ * @brief Перезагружает модель из базы данных.
+ */
void updateModel();
+ /**
+ * @brief Возвращает запись клиента по индексу строки.
+ */
LicenseItem getItem(int index) const;
signals:
+ /**
+ * @brief Сигнал изменения статуса модели.
+ */
void statusChanged();
private:
+ /**
+ * @brief Проверяет наличие таблиц БД.
+ */
bool checkTables();
private:
- QList m_data;
- Status m_status = Status::None;
- QStringList m_errors;
+ QList m_data; //!< Кэш данных для отображения.
+ Status m_status = Status::None; //!< Текущий статус модели.
+ QStringList m_errors; //!< Список ошибок работы с БД.
};
#endif // LICENSEMANAGER_LICENSEMODEL_H
diff --git a/src/MainWidget/MainWidget.cpp b/src/MainWidget/MainWidget.cpp
index bdab7e0..a241a86 100644
--- a/src/MainWidget/MainWidget.cpp
+++ b/src/MainWidget/MainWidget.cpp
@@ -57,11 +57,14 @@ MainWidget::MainWidget(QWidget *parent)
{
if (!m_licenseModel->prepareDatabase())
{
- QMessageBox::critical(
- this
- , tr("Error")
- , tr("Database create error")
- );
+ QMessageBox messageBox;
+ messageBox.setIcon(QMessageBox::Critical);
+ messageBox.setWindowTitle(tr("Error"));
+ messageBox.setText(tr("Database create error"));
+ messageBox.setDetailedText(m_licenseModel->getStatusText());
+ messageBox.exec();
+
+ QApplication::quit();
}
}
else if (result == QMessageBox::Button::No)
@@ -110,6 +113,8 @@ MainWidget::MainWidget(QWidget *parent)
m_menu->addSeparator();
m_getLicenseFileAction = m_menu->addAction(QIcon(QStringLiteral(":/deps/getLicenseFile.png")), tr("Get license file"));
+ connect(m_deleteClientToolBarAction, &QAction::triggered, this, &MainWidget::onDeleteClientTriggered);
+ connect(m_editClientToolBarAction, &QAction::triggered, this, &MainWidget::onEditClientTriggered);
connect(m_reloadTableMenuAction, &QAction::triggered, this, &MainWidget::onReloadTableTriggered);
connect(m_addClientsMenuAction, &QAction::triggered, this, &MainWidget::onAddClientTriggered);
connect(m_editClientsMenuAction, &QAction::triggered, this, &MainWidget::onEditClientTriggered);
diff --git a/src/MainWidget/MainWidget.h b/src/MainWidget/MainWidget.h
index d1cf6c0..13de811 100644
--- a/src/MainWidget/MainWidget.h
+++ b/src/MainWidget/MainWidget.h
@@ -1,6 +1,15 @@
#ifndef LICENSEMANAGER_MAINWIDGET_H
#define LICENSEMANAGER_MAINWIDGET_H
+/**
+ * @file MainWidget.h
+ * @brief Интерфейс главного окна приложения.
+ *
+ * @details
+ * Содержит декларацию главного окна, управляющего таблицей клиентов,
+ * контекстным меню, действиями панели инструментов и созданием лицензий.
+ */
+
// Qt
#include
#include
@@ -18,44 +27,89 @@ class MainWidget : public QMainWindow
{
Q_OBJECT
public:
+ /**
+ * @brief Создаёт главное окно и инициализирует интерфейс.
+ */
explicit MainWidget(QWidget *parent = nullptr);
+ /**
+ * @brief Завершает работу окна и сохраняет настройки.
+ */
~MainWidget() override;
protected:
+ /**
+ * @brief Перехватывает закрытие окна для подтверждения выхода.
+ */
void closeEvent(QCloseEvent *event) override;
private slots:
+ /**
+ * @brief Показывает контекстное меню таблицы.
+ */
void onCustomContextMenuRequested(const QPoint &pos);
+ /**
+ * @brief Отображает диалог с информацией о версии/репозитории.
+ */
void onGetInfoTriggered();
+ /**
+ * @brief Перезагружает данные модели из базы.
+ */
void onReloadTableTriggered();
+ /**
+ * @brief Открывает диалог добавления клиента.
+ */
void onAddClientTriggered();
+ /**
+ * @brief Открывает диалог редактирования выбранного клиента.
+ */
void onEditClientTriggered();
+ /**
+ * @brief Удаляет выбранные строки клиентов.
+ */
void onDeleteClientTriggered();
+ /**
+ * @brief Генерирует и сохраняет файл лицензии для выбранного клиента.
+ */
void onGetLicenseFileTriggered();
+ /**
+ * @brief Обрабатывает закрытие диалога редактирования клиента.
+ */
void onEditClientDialogClosed(int result);
+ /**
+ * @brief Обновляет доступность действий при смене выбора.
+ */
void selectionChanged();
+ /**
+ * @brief Обновляет статусную строку при изменении состояния модели.
+ */
void modelStatusChanged();
private:
+ /**
+ * @brief Сохраняет параметры окна (размер, положение).
+ */
void saveSettings();
+ /**
+ * @brief Загружает параметры окна.
+ */
void loadSettings();
private:
- QTableView* m_tableView{nullptr};
- LicenseModel* m_licenseModel{nullptr};
- QToolBar* m_toolBar{nullptr};
- QMenu* m_menu{nullptr};
- QAction* m_addClientToolBarAction{nullptr};
- QAction* m_editClientToolBarAction{nullptr};
- QAction* m_deleteClientToolBarAction{nullptr};
- QAction* m_infoToolBarAction{nullptr};
- QAction* m_reloadTableMenuAction{nullptr};
- QAction* m_addClientsMenuAction{nullptr};
- QAction* m_deleteClientsMenuAction{nullptr};
- QAction* m_editClientsMenuAction{nullptr};
- QAction* m_getLicenseFileAction{nullptr};
- EditClientDialog* m_editClientDialog{nullptr};
- QStatusBar* m_statusBar{nullptr};
- QLabel* m_statusLabel{nullptr};
+ QTableView* m_tableView{nullptr}; //!< Таблица с клиентами.
+ LicenseModel* m_licenseModel{nullptr}; //!< Модель данных лицензий.
+ QToolBar* m_toolBar{nullptr}; //!< Панель инструментов.
+ QMenu* m_menu{nullptr}; //!< Контекстное меню таблицы.
+ QAction* m_addClientToolBarAction{nullptr}; //!< Действие добавления клиента.
+ QAction* m_editClientToolBarAction{nullptr}; //!< Действие редактирования клиента.
+ QAction* m_deleteClientToolBarAction{nullptr}; //!< Действие удаления клиента.
+ QAction* m_infoToolBarAction{nullptr}; //!< Действие показа информации.
+ QAction* m_reloadTableMenuAction{nullptr}; //!< Действие обновления таблицы.
+ QAction* m_addClientsMenuAction{nullptr}; //!< Действие добавления из меню.
+ QAction* m_deleteClientsMenuAction{nullptr}; //!< Действие удаления из меню.
+ QAction* m_editClientsMenuAction{nullptr}; //!< Действие редактирования из меню.
+ QAction* m_getLicenseFileAction{nullptr}; //!< Действие сохранения файла лицензии.
+ EditClientDialog* m_editClientDialog{nullptr}; //!< Диалог редактирования клиента.
+ QStatusBar* m_statusBar{nullptr}; //!< Строка состояния.
+ QLabel* m_statusLabel{nullptr}; //!< Текст статуса модели.
};
#endif // LICENSEMANAGER_MAINWIDGET_H
diff --git a/src/def.h b/src/def.h
index d361355..60b7385 100644
--- a/src/def.h
+++ b/src/def.h
@@ -1,10 +1,25 @@
#ifndef LICENSEMANAGER_DEF_H
#define LICENSEMANAGER_DEF_H
+/**
+ * @file def.h
+ * @brief Общие константы и базовые настройки приложения.
+ *
+ * @details
+ * Содержит глобальные значения, используемые в разных частях проекта:
+ * путь к базе данных и имя организации для QSettings.
+ */
+
#include
#include
+/**
+ * @brief Путь к SQLite базе данных приложения.
+ */
const static QString DB_PATH = "./db.sqlite";
+/**
+ * @brief Имя организации для QSettings.
+ */
const static QString ORGANIZATION_NAME = "LicenseManager";
-#endif //LICENSEMANAGER_DEF_H
\ No newline at end of file
+#endif //LICENSEMANAGER_DEF_H