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 | App | `deps/icon.png` | +| Reload table | Reload | `deps/reload.png` | +| Add client | Add | `deps/add.png` | +| Edit client | Edit | `deps/edit.png` | +| Delete client | Delete | `deps/delete.png` | +| Get info | Info | `deps/info.png` | +| Get license file | License file | `deps/getLicenseFile.png` | +| Drop files (автозаполнение) | 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