Побег из КриптоПро (ч.4, final?)
В третьей части цикла статей "Побег из КриптоПро" был разобран экспорт приватного ключа из проприетарного формата ключевого контейнера (те самые 6 файлов .key), а в цикле "Тащим ключи с Рутокен Lite" собственно был разобран экспорт самого контейнера с токена. Однако остался главный нерешённый вопрос, из-за которого экспорт контейнера штатными средствами "Панели администрирования Рутокен" и КриптоПро был невозможен - флаг экспортируемости контейнера.
Важно: не следует путать понятие неэкспортируемости и неизвлекаемости. Неэкспортируемый контейнер - контейнер, на который наложено софтовое ограничение на экспорт, т.е если программа проигнорирует этот флаг, то она сможет произвести экспорт. Понятие неизвлекаемости относится к токенам с механизмом ФКН (Функциональный ключевой носитель, аля PKCS#11), где сам ключ генерируется на токене и никогда не покидает его (такая возможность попросту отсутствует), все операции вплоть до вычисления хэшей выполняются на самом токене.
Его величество - флаг...
Почему флаг? - Потому, что как оказалась всё софтовое ограничение, запрещающее экспорт, представляет из себя значение в 1 бит. Т.е "1" - экспорт разрешён, а "0" - экспорт запрещён. Такие дела...
А если подробнее?
Давайте взглянем на файл header.key с помощью декодера ASN.1:
Мы видим всю открытую информацию о контейнере, например байты сертификата под тегом 5, или кусочек публичного ключа под тегом 8, или HMAC для проверки пароля контейнера под тегом 2. Но в данном случае нас интересуют другие данные, а именно 2 SEQUENCE (выделены оранжевым и зелёным). В одном из них мы можем заметить BIT STRING с 3 битами (выделены жёлтым). Данные биты - это параметры приватного ключа. Собственно первый бит и есть флаг экспортируемости. Вообще вся структура там следующая:
Однако, что за значение выделенное красным? - Это MAC всего контейнера, если мы просто изменим флаг, но не пересчитаем MAC, то КриптоПро такой контейнер не примет. (Кстати подобная схема используется и в экспортном представлении приватного ключа)
Пересчитываем MAC контейнера
Сам алгоритм вычисления MAC довольно прост:
- Берём SEQUENCE выделенный оранжевым и сериализуем его в байты;
- Вычисляем MAC с помощью шифра Магма (ГОСТ 28147-89) со следующими параметрами:
- Ключ: 32 нулевых байта
- IV/Соль: 8 нулевых байт
- Данные: сериализованый ранее SEQUENCE
- Набор параметров (SBox) -ID_TC26_GOST_28147_PARAM_Z - Подставляем получившейся в MAC (если MAC больше 4 байт то берём только первые 4 байта) в ASN.1 структуру, сериализуем в байты и записываем их в файл
- Profit!
В результате изменения бита в параметрах приватного ключа и применения алгоритма вычисления MAC контейнера мы смогли успешно "пролечить" контейнер разрешив экспорт.
Бонус. Как вычисляются остальные MAC
MAC маски и соли
Вычисляем MAC с помощью шифра Магма (ГОСТ 28147-89) со следующими параметрами:
- Ключ: Значение маски (выделено жёлтым, если больше 32 байтов, то берём последние 32 байта)
- IV/Соль: 8 нулевых байт
- Данные: Значение соли (выделено оранжевым)
- Набор параметров (SBox) - ID_TC26_GOST_28147_PARAM_Z
Если MAC больше 4 байт то берём только первые 4 байта
MAC пароля
- Прогоняем через CPKDF (CryptoPro Key derivation function) пароль (в качестве соли для CPKDF берём 8 байт куска публичного ключа (в ASN.1 под тегом 10, на рисунке выделено жёлтым)
- Вычисляем MAC с помощью шифра Магма (ГОСТ 28147-89) со следующими параметрами:
- Ключ: Результат из п.1
- IV/Соль: 8 нулевых байт
- Данные: 16 нулевых байт
- Набор параметров (SBox) -ID_TC26_GOST_28147_PARAM_Z - Если MAC больше 4 байт то берём только первые 4 байта
На этом у меня всё, все ASN.1 схемы, а также практические реализации алгоритмов вычисления MAC вы можете найти в исходном коде @li0ard/cpfx. Также туда в скором времени выйдет обновление добавляющее автоматическое изменение флага экспортируемости с пересчётом MAC.