Введение
Протокол Kerberos 5 сейчас активно используется для аутентификации. Особенностью данного протокола является то, что он осуществляет аутентификацию, базируясь на четырех китах:
- Симметричное шифрование;
- Хеширование;
- ЭЦП;
- Третья доверенная сторона.
Начиная с пятой версии появилась возможность использовать еще асимметричное шифрование (для электронной подписи). Более подробно на работе протокола Kerberos останавливаться не имеет смысла, ибо описание алгоритма можно посмотреть тут.
К сожалению, количество алгоритмов шифрования, хеширования и ЭЦП, которые использует данный протокол, не настолько велико, насколько хотелось бы, поэтому в данной статье я хочу показать, как добавить легко и просто собственные алгоритмы в реализацию данного протокола MIT'ом. Добавлять же мы будем наши отечественные алгоритмы: ГОСТ 28147-89 (aka Магма), ГОСТ Р 34.11-2012 (aka Стрибог) и ГОСТ Р 34.10-2012 (хотелось бы тоже иметь для него aka, но я не знаю:(). Готовое решение для данных алгоритмов можно его найти в моем репозитории. На стороне клиента мы будем использовать аппаратные реализации алгоритмов ГОСТ в Рутокене ЭЦП 2.0 и их программные реализации в engine GOST для openssl. Но самый безопасный вариант хранения ключей – когда они генерируются непосредственно на Рутокене и никогда не покидают его память во время криптографических операций. Для такого варианта работы потребуется rtengine.
Перед тем, как приступить к внедрению алгоритмов, опишем основные места, где будут производится изменения. Внутри директории src/lib/crypto/ находятся реализации всех алгоритмов, отвечающих за симметричную криптографию и хеширование. В ней имеются 2 реализации этих криптографических алгоритмов: встроенная(builtin) и openssl. Дабы сэкономить время и место, мы, естественно, будем добавлять реализации алгоритмов с помощью openssl, в котором они уже есть (ну или почти есть, но об этом чуть позже). Для добавления же асимметричных алгоритмов, нужно будет подправить плагин src/plugins/preauth/pkinit
Если у вас еще не настроен Kerberos, то инструкцию по его первичной настройки и эксплуатации можно взять тут. В дальнейшем автор полагает, что вы работаете с доменом AKTIV-TEST.RU
Добавление алгоритмов хеширования и симметричного шифрования
Как уже было ранее озвучено, мы не будем писать алгоритмы шифрования и хеширования с нуля, а воспользуемся готовой реализацией данных алгоритмов в openssl. Напрямую openssl не поддерживает в себе реализацию отечественных алгоритмов, но обладает мобильностью в данном вопросе и позволяет добавлять новые алгоритмы, используя механизм engine GOST для работы с ключами в файловой системе и, хранящие на токене — rtengine.
Небольшое введение в механизм engine openssl и подключение engine GOST
Engine в openssl – это небольшая динамическая библиотека, которую openssl подгружает в runtime по требованию. Каждая такая библиотека, должна содержать в себе определенные символы (функции) для загрузки необходимых алгоритмов. В данной работе мы воспользуемся engine gost, который содержит в себе все необходимые отечественные криптографические алгоритмы.
Установка дичайше проста, например, и происходит следующим образом:
Выкачиваете из репозитория реализацию данного энджина.
собираете библиотеку с ним (mkdir build && cd build && cmake… && make):
mkdir build cd build cmake .. make
В директории bin (КОТОРАЯ ПОЯВИТСЯ В КОРНЕВОМ КАТАЛОГЕ ПРОЕКТА!!!) будет лежать динамическая библиотека gost.so – это и есть наш энджин. Его нужно будет перенести в директорию, где хранятся энджины openssl. Узнать месторасположение данной директории можно с помощью:
openssl version -a | grep ENGINESDIR
Дело за последним – нужно сказать openssl, где находится данный энджин и как он называется. Сделать можно это с помощью изменения файла конфигурации openssl.cnf. Месторасположение которого можно узнать с помощью:
openssl version -a | grep OPENSSLDIR
В конец данного файла нужно будет внести следующее содержимое:
# в начало файла написать openssl_conf = openssl_def ... # в конец файла # OpenSSL default section [openssl_def] engines = engine_section # Engine section [engine_section] gost = gost_section # Engine gost section [gost_section] engine_id = gost dynamic_path = /path/to/engines/dir/with/gost.so default_algorithms = ALL init = 0
После этого данный энджин должен появится в openssl. Проверить его работоспособность можно заставив, например, сгенерировать приватный ключ в файле по ГОСТ Р 34.10-2012:
openssl genpkey -engine gost -algorithm gost2012_512 -pkeyopt paramset:A -out client_key.pem
Флаг -engine как раз говорит о том, какой engine нужно подгрузить перед началом работы для того, чтобы стал виден алгоритм генерации ключей для ГОСТ Р 34.10-2012.
Реализация алгоритма хеширования
Начнем с самого простого – с реализации алгоритма Стрибог. Kerberos обладает жесткой связью алгоритма хеширования и шифрования, то есть вы не можете просто так взять и выбрать для шифрования один алгоритм, а для хеширования другой – вам нужно будет встроить связку алгоритма хеширования и шифрования. Причина этого мне не известна, но раз там бытуют такие правила – давайте попробуем создать связку алгоритма Стрибог и AES.
Т.к. нам понадобится уверенность в подключении в разных местах нашей программы, давайте создадим сначала небольшую библиотеку gost_helper, которая будет содержать в себе функцию инициализации энджина в openssl, а так же для удобства несколько функций, возвращающих контексты некоторых алгоритмов хеширования – нам это поможет в дальнейшем. Назовем эту библиотеку gost_helper и создадим для нее соответствующий заголовочник и исходный файл в директории src/lib/crypto/openssl/:
// gost_helper.h #include <openssl/evp.h> // функция инициализация энджина GOST void krb5int_init_gost(); // вспомогательные функции, возвращающие контексты алгоритмов хеширования const EVP_MD * EVP_gostR3411_2012_256(); const EVP_MD * EVP_gostR3411_2012_512(); // gost_helper.c #include "gost_helper.h" #include <openssl/engine.h> // указатель на подключенный энджин static ENGINE *eng = NULL; void krb5int_init_gost() { // для ускорения работы проверяем, не подключен ли энджин уже if (eng) return; OPENSSL_add_all_algorithms_conf(); ERR_load_crypto_strings(); if (!(eng = ENGINE_by_id("gost"))) { printf("Engine gost doesn’t exist"); return; } ENGINE_init(eng); ENGINE_set_default(eng, ENGINE_METHOD_ALL); } const EVP_MD * EVP_gostR3411_2012_256() { krb5int_init_gost(); return EVP_get_digestbynid(NID_id_GostR3411_2012_256); } const EVP_MD * EVP_gostR3411_2012_512() { krb5int_init_gost(); return EVP_get_digestbynid(NID_id_GostR3411_2012_512); }
После добавления вспомогательной библиотеки необходимо будет объявить о ее существовании в Makefile и выписать ее зависимости от файлов. Для этого добавим следующее:
# добавим в шаблон src/lib/crypto/openssl/Makefile.in описание зависимостей объектных файлов от исходников: STLIBOBJS=hmac.o ... stubs.o gost_helper.o OBJS=$(OUTPRE)hmac.$(OBJEXT) ... $(OUTPRE)stubs.$(OBJEXT) $(OUTPRE)gost_helper$(OBJEXT) SRCS=$(srcdir)/hmac.c ... $(srcdir)/stubs.c $(srcdir)/gost_helper.c # В файл зависимостей src/lib/crypto/openssl/deps добавим, от чего будут зависеть наши объектные файлы и библиотеки: gost_helper.so gost_helper.po $(OUTPRE)gost_helper.$(OBJEXT): $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h $(top_srcdir)/include/k5-err.h $(top_srcdir)/include/k5-gmt_mktime.h $(top_srcdir)/include/k5-int-pkinit.h $(top_srcdir)/include/k5-int.h $(top_srcdir)/include/k5-platform.h $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-thread.h $(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h gost_helper.c gost_helper.h
Стоит заметить, что в дальнейшем при подключении данной библиотеки, нам надо будет добавлять зависимости некоторых файлов от заголовочника данной библиотеки. Делается это очень просто – отыскивается цель, куда надо добавить зависимость в deps файле и записывается зависимость от rel/path/to/gost_helper.h. Например, в src/lib/crypto/openssl/hash_provider/deps нужно будет добавить следующее:
hash_evp.so hash_evp.po $(OUTPRE)hash_evp.$(OBJEXT): $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h ... $(srcdir)/../gost_helper.h hash_evp.c
В целях экономии места в статье и чтобы размять ваши мозги, я больше не буду обращать на это внимание: будьте внимательны и осторожны!
Добавим теперь реализации функций хеширования, все их реализации у нас лежат в src/lib/crypto/openssl/hash_provider/hash_evp.c. Туда нужно будет дописать следующее:
// Не забываем про изменение deps файла!!! Так уж и быть на первый раз предупрежу невнимательных #include "crypto_int.h" #include "gost_helper.h" #include <openssl/evp.h> ... static krb5_error_code hash_sha384(const krb5_crypto_iov *data, size_t num_data, krb5_data *output) { return hash_evp(EVP_sha384(), data, num_data, output); } static krb5_error_code hash_stribog256(const krb5_crypto_iov *data, size_t num_data, krb5_data *output) { return hash_evp(EVP_gostR3411_2012_256(), data, num_data, output); } static krb5_error_code hash_stribog512(const krb5_crypto_iov *data, size_t num_data, krb5_data *output) { return hash_evp(EVP_gostR3411_2012_512(), data, num_data, output); } // структура функции хеширования /* первый атрибут -- название алгоритма второй -- размер хеша в байтах третий -- размер блока передаваемых данных в байтах четвертый -- указатель на функцию хеширования */ const struct krb5_hash_provider krb5int_hash_sha384 = { "SHA-384", 48, 128, hash_sha384 }; const struct krb5_hash_provider krb5int_hash_stribog256 = { "GOSTR34.11-2012-256", 32, 64, hash_stribog256 }; const struct krb5_hash_provider krb5int_hash_stribog512 = { "GOSTR34.11-2012-512", 64, 64, hash_stribog512 };
Теперь надо объявить эти контексты во всей библиотеке. Для этого нужно создать их описание в файле src/lib/crypto/krb/crypto_int.h.
// Параллельно заметим, что компилятор начнет ругаться, что имя функций не помещается в указанный массив, поэтому немного поправим структуру krb5_hash_provider и добавим кол-во символов в массиве имени алгоритма: ... struct krb5_hash_provider { char hash_name[32]; // было 8 size_t hashsize, blocksize; krb5_error_code (*hash)(const krb5_crypto_iov *data, size_t num_data, krb5_data *output); }; ... extern const struct krb5_hash_provider krb5int_hash_sha384; extern const struct krb5_hash_provider krb5int_hash_stribog256; extern const struct krb5_hash_provider krb5int_hash_stribog512; ...
Объявим идентификатор связки Стрибог и AES, внедря макросы, которые мы назовем CKSUMTYPE_STRIBOG_256_AES256, CKSUMTYPE_STRIBOG_512_AES256, ENCTYPE_AES256_CTS_STRIBOG_256, ENCTYPE_AES256_CTS_STRIBOG_512. Их надо объявить в шаблоне заголовочного файла src/include/krb5/krb5.hin. Выглядеть это будет примерно так:
#define ENCTYPE_ARCFOUR_HMAC_EXP 0x0018 /**< RFC 4757 */ #define ENCTYPE_CAMELLIA128_CTS_CMAC 0x0019 /**< RFC 6803 */ #define ENCTYPE_CAMELLIA256_CTS_CMAC 0x001a /**< RFC 6803 */ #define ENCTYPE_AES256_CTS_STRIBOG_256 0x001b /**< NO RFC */ #define ENCTYPE_AES256_CTS_STRIBOG_512 0x001c /**< NO RFC */ #define ENCTYPE_UNKNOWN 0x01ff ... #define CKSUMTYPE_CMAC_CAMELLIA256 0x0012 /**< RFC 6803 */ #define CKSUMTYPE_MD5_HMAC_ARCFOUR -137 /* Microsoft netlogon */ #define CKSUMTYPE_HMAC_MD5_ARCFOUR -138 /**< RFC 4757 */ #define CKSUMTYPE_STRIBOG_256_AES256 -139 /**< NO RFC */ #define CKSUMTYPE_STRIBOG_512_AES256 -140 /**< NO RFC */
Теперь необходимо добавить две связки функции хеширования и шифрования и функции шифрования и хеширования. Зачем две, если они эквиваленты, спросите вы? Ответ: не знаю, или это исторический костыль или способ оптимизации. Тем не менее, давайте добавим в необходимые файлы новые структуры:
// в файле src/lib/crypto/krb/cksumtypes.c добавить в конец списка структуру алгоритмов хеширования и шифрования /* первый атрибут -- идентификатор второй -- название связки в третьем -- можно добавить алиасы связок в четвертом -- описание в пятом -- указатель на функцию шифрования в шестом -- указатель на функцию хеширования в седьмом -- указатель на функцию "работы с хешом" в восьмом -- размер блока в девятом -- размер хеша в десятом -- различные флаги */ const struct krb5_cksumtypes krb5int_cksumtypes_list[] = { ... { CKSUMTYPE_STRIBOG_256_AES256, "stribog-256-aes256", { 0 }, "STRIBOG256 AES256 key", &krb5int_enc_aes256, &krb5int_hash_stribog256, krb5int_etm_checksum, NULL, 64, 32, 0 }, { CKSUMTYPE_STRIBOG_512_AES256, "stribog-512-aes256", { 0 }, "STRIBOG512 AES256 key", &krb5int_enc_aes256, &krb5int_hash_stribog512, krb5int_etm_checksum, NULL, 64, 64, 0 }, }; // в файле src/lib/crypto/krb/etypes.c добавить в конец списка структуру алгоритмов шифрования и хеширования: /* первый атрибут -- идентификатор второй -- название третий -- алиас четвертый -- описание пятый -- указатель на функцию шифрования шестой -- указатель на функцию хеширования седьмой -- размер возвращаемых данных функцией получения случайных чисел из данных восьмой -- указатель на функцию, возвращающую характеристики алгоритмов девятый -- указатель на функцию шифрования, использующую алгоритмы хеширования и шифрования десятый -- указатель на функцию расшифрования, использующую алгоритмы хеширования и шифрования одиннадцатый -- указатель на функцию преобразования строки в ключ двенадцатый -- указатель на функцию выработки случайного ключа тринадцатый -- различные флаги четырнадцатый -- размер ключа */ const struct krb5_keytypes krb5int_enctypes_list[] = { ... { ENCTYPE_AES256_CTS_STRIBOG_256, "aes256-cts-stribog-256", { "aes256-stribog256" }, "AES-256 CTS mode with 256-bit stribog", &krb5int_enc_aes256, &krb5int_hash_stribog256, 16, krb5int_aes2_crypto_length, krb5int_etm_encrypt, krb5int_etm_decrypt, krb5int_aes2_string_to_key, k5_rand2key_direct, krb5int_aes2_prf, CKSUMTYPE_STRIBOG_256_AES256, 0 /*flags*/, 256 }, { ENCTYPE_AES256_CTS_STRIBOG_512, "aes256-cts-stribog-512", { "aes256-stribog512" }, "AES-256 CTS mode with 512-bit stribog", &krb5int_enc_aes256, &krb5int_hash_stribog512, 16, krb5int_aes2_crypto_length, krb5int_etm_encrypt, krb5int_etm_decrypt, krb5int_aes2_string_to_key, k5_rand2key_direct, krb5int_aes2_prf, CKSUMTYPE_STRIBOG_512_AES256, 0 /*flags*/, 256 }, };
Казалось бы уже все, а вот и нет! Далее наступают проблемы, которые будут видны только в процессе отладки, некоторых из них можно было бы избежать, указав, например, другие указатели на функции в вышеуказанных структурах, но мы пойдем более сложным путем, чтобы показать, что еще возможно придется исправить по дороге. Обо всех этих проблемах я узнал только пользуясь отладчиком:
// В файле src/lib/crypto/openssl/hmac.c будет задействоваться функция map_digest -- которая возвращает контекст хеш-функции по переданной структуре. Сейчас эта функция ничего не знает о наших ГОСТах. Исправим это: #include "crypto_int.h" #include "gost_helper.h" #include <openssl/hmac.h> #include <openssl/evp.h> static const EVP_MD * map_digest(const struct krb5_hash_provider *hash) { if (!strncmp(hash->hash_name, "SHA1",4)) return EVP_sha1(); ... else if (!strncmp(hash->hash_name, "GOSTR34.11-2012-256", 19)) return EVP_gostR3411_2012_256(); else if (!strncmp(hash->hash_name, "GOSTR34.11-2012-512", 19)) return EVP_gostR3411_2012_512(); else return NULL; } // В файле src/lib/crypto/openssl/pbkdf2.c вызывается krb5int_pbkdf2_hmac, которая сейчас ничего не знает о новых хешах: krb5_error_code krb5int_pbkdf2_hmac(const struct krb5_hash_provider *hash, const krb5_data *out, unsigned long count, const krb5_data *pass, const krb5_data *salt) { const EVP_MD *md = NULL; /* Get the message digest handle corresponding to the hash. */ if (hash == &krb5int_hash_sha1) md = EVP_sha1(); ... else if (hash == &krb5int_hash_stribog256) md = EVP_gostR3411_2012_256(); else if (hash == &krb5int_hash_stribog512) md = EVP_gostR3411_2012_512(); ... return 0; } // в файле src/lib/krb5/krb/init_ctx.c нужно добавить в список связок алгоритмов, идентификатор новых алгоритмов: static krb5_enctype default_enctype_list[] = { ... ENCTYPE_AES256_CTS_STRIBOG_256, ENCTYPE_AES256_CTS_STRIBOG_512, 0 };
После всех этих изменений можно проверить работу алгоритма. Соберем все, что мы наделали.
autoconf
./configure --with-crypto-impl=openssl # использовать реализацию алгоритмов из openssl
make
sudo make install
Теперь приступим к проверке. Для этого давайте выставим принудительное использование внедренных нами алгоритмов в файлы конфигурации Kerberos. Выполните следующее:
Остановите krb5kdc:
service krb5-kdc stop
Подправим файл конфигурации kdc.conf (у меня это /usr/local/var/krb5kdc/kdc.conf). Выставим принудительное хеширование с помощью нововведенного алгоритма:
[realms] AKTIV-TEST.RU = { master_key_type = aes256-stribog512 supported_enctypes = aes256-stribog512:normal default_tgs_enctypes = aes256-stribog512 default_tkt_enctypes = aes256-stribog512 permitted_enctypes = aes256-stribog512 } # Так же можно использовать хеш длиной 256 бит
Аналогичные изменения в файле конфигурации всего протокола krb5.conf (у меня он находится по адресу /etc/krb5.conf):
[libdefaults] supported_enctypes = aes256-stribog512:normal default_tgs_enctypes = aes256-stribog512 default_tkt_enctypes = aes256-stribog512 permitted_enctypes = aes256-stribog512 # Также можно использовать хеш длиной 256 бит
Далее запустим krb5kdc т.к. изменился master_key, то возможно придется создать базу данных principals заново с помощью krb5_newrealm.
После этого создаем всех необходимых принципалов и можно начать работу. Попробуйте аутентифицироваться с помощью kinit.
Проверим, что хеширование происходит по указанному алгоритму можно с
помощью klist -e.
Если сервис не запускается, то его можно запустить из-под рута с помощью src/kdc/krb5kdc. Если все запустилось, прошло гладко – поздравляю вас! Иначе – увы, панацею от всех проблем я не предлагаю, а лишь предлагаю "небольшую" инструкцию, содержащую основные шаги, которые вам придется совершить, дабы внедрить новый алгоритм в Kerberos. И если у вас ничего не вышло – берите в руки gdb и смотрите, где что выходит не так. Вам лишь могу дать несколько советов:
- собирать проект в режиме отладки можно, если передать в ./configure CFLAGS=" -g -O0";
- запустить krb5kdc не в фоновом режиме можно с помощью флага -n;
- надеюсь до этого не дойдет (хотя при реализации асимметричных алгоритмов у меня все-таки дошло) – возможно вам потребуются отладочные символы openssl – установите их или взяв из репозитория, или установив openssl из исходников с отладочными символами;
- то же самое касается gost engine.
По идее этого набора советов вам должно хватить для того чтобы избавить себя от лишней траты времени в поисках "неизведанного".
Реализация алгоритма шифрования
В этом разделе я покажу, как можно добавить свой алгоритм шифрования данных в Kerberos, и мы попробуем создать связку Магмы и Стрибога, добавленного в прошлом разделе. Тут я уже предполагаю, что небольшая библиотека gost_helper уже была добавлена в прошлом разделе. Ну что же, разминаем пальцы и приступаем:
Сначала опишем алгоритм в нашей библиотеке libk5crypto описав их в заголовочном файле src/lib/crypto/krb/crypto_int.h.
... extern const struct krb5_enc_provider krb5int_enc_camellia256; extern const struct krb5_enc_provider krb5int_enc_gost89; ...
В директории src/lib/crypto/openssl/enc_provider добавим исходник gost.c, содержащий в себе реализацию всех необходимых алгоритмов (за основу я взял исходник алгоритма des). Важно заметить, что мы реализуем только режим шифрования cbc, так что для самопроверки вы можете взять любой другой режим шифрования и добавить его:
#include "crypto_int.h" #include "gost_helper.h" #include <openssl/evp.h> #define BLOCK_SIZE 8 static krb5_error_code krb5int_gost_encrypt(krb5_key key, const krb5_data *ivec, krb5_crypto_iov *data, size_t num_data) { int ret, olen = BLOCK_SIZE; unsigned char iblock[BLOCK_SIZE], oblock[BLOCK_SIZE]; struct iov_cursor cursor; EVP_CIPHER_CTX *ctx; // я бы перенес вызов этой функции в функцию инициализации состояния, но функция krb5int_gost_encrypt, иногда вызывается без нее: krb5int_init_gost(); ctx = EVP_CIPHER_CTX_new(); if (ctx == NULL) return ENOMEM; ret = EVP_EncryptInit_ex(ctx, EVP_get_cipherbynid(NID_gost89_cbc), NULL, key->keyblock.contents, (ivec) ? (unsigned char*)ivec->data : NULL); if (!ret) { EVP_CIPHER_CTX_free(ctx); return KRB5_CRYPTO_INTERNAL; } EVP_CIPHER_CTX_set_padding(ctx,0); k5_iov_cursor_init(&cursor, data, num_data, BLOCK_SIZE, FALSE); while (k5_iov_cursor_get(&cursor, iblock)) { ret = EVP_EncryptUpdate(ctx, oblock, &olen, iblock, BLOCK_SIZE); if (!ret) break; k5_iov_cursor_put(&cursor, oblock); } if (ivec != NULL) memcpy(ivec->data, oblock, BLOCK_SIZE); EVP_CIPHER_CTX_free(ctx); zap(iblock, sizeof(iblock)); zap(oblock, sizeof(oblock)); if (ret != 1) return KRB5_CRYPTO_INTERNAL; return 0; } static krb5_error_code krb5int_gost_decrypt(krb5_key key, const krb5_data *ivec, krb5_crypto_iov *data, size_t num_data) { int ret, olen = BLOCK_SIZE; unsigned char iblock[BLOCK_SIZE], oblock[BLOCK_SIZE]; struct iov_cursor cursor; EVP_CIPHER_CTX *ctx; krb5int_init_gost(); ctx = EVP_CIPHER_CTX_new(); if (ctx == NULL) return ENOMEM; ret = EVP_DecryptInit_ex(ctx, EVP_get_cipherbynid(NID_gost89_cbc), NULL, key->keyblock.contents, (ivec) ? (unsigned char*)ivec->data : NULL); if (!ret) { EVP_CIPHER_CTX_free(ctx); return KRB5_CRYPTO_INTERNAL; } EVP_CIPHER_CTX_set_padding(ctx,0); k5_iov_cursor_init(&cursor, data, num_data, BLOCK_SIZE, FALSE); while (k5_iov_cursor_get(&cursor, iblock)) { ret = EVP_DecryptUpdate(ctx, oblock, &olen, (unsigned char *)iblock, BLOCK_SIZE); if (!ret) break; k5_iov_cursor_put(&cursor, oblock); } if (ivec != NULL) memcpy(ivec->data, iblock, BLOCK_SIZE); EVP_CIPHER_CTX_free(ctx); zap(iblock, sizeof(iblock)); zap(oblock, sizeof(oblock)); if (ret != 1) return KRB5_CRYPTO_INTERNAL; return 0; } static krb5_error_code krb5int_gost_init_state (const krb5_keyblock *key, krb5_keyusage usage, krb5_data *state) { state->length = 8; state->data = (void *) malloc(8); if (state->data == NULL) return ENOMEM; memset(state->data, 0, state->length); return 0; } static void krb5int_gost_free_state(krb5_data *state) { free(state->data); *state = empty_data(); } /* первый атрибут -- как неожиданно, размер блока второй -- кол-во байтов для хранения первичного ключа третий -- размер байтов для хранения развернутого ключа четвертый -- указатель на функцию шифрования пятый -- указатель на функцию расшифровки шестой -- указатель на функцию cbc-mac checksum, не вдавался в подробности что это, т.к. наш алгоритм этого не имеет седьмой -- указатель на функцию инициализации шифрования восьмой -- указатель на функцию освобождения памяти под первичное состояние */ const struct krb5_enc_provider krb5int_enc_gost89 = { BLOCK_SIZE, 32, 32, krb5int_gost_encrypt, krb5int_gost_decrypt, NULL, krb5int_gost_init_state, krb5int_gost_free_state };
В шаблоне src/lib/crypto/openssl/enc_provider/Makefile.in укажем, что появился новый исходник:
STLIBOBJS= des.o ... gost.o OBJS= $(OUTPRE)des.$(OBJEXT) ... $(OUTPRE)gost$(OBJEXT) SRCS= $(srcdir)/des.c ... $(srcdir)/gost.c
Не забываем про указание зависимостей в src/lib/crypto/openssl/enc_provider/deps:
gost.so gost.po $(OUTPRE)gost.$(OBJEXT): $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(srcdir)/../../krb/crypto_int.h $(srcdir)/../crypto_mod.h $(top_srcdir)/include/k5-buf.h $(top_srcdir)/include/k5-err.h $(top_srcdir)/include/k5-gmt_mktime.h $(top_srcdir)/include/k5-int-pkinit.h $(top_srcdir)/include/k5-int.h $(top_srcdir)/include/k5-platform.h $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-thread.h $(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h $(top_srcdir)/include/socket-utils.h $(srcdir)/../gost_helper.h gost.c
Добавим идентификаторы связок к src/include/krb5/krb5.hin:
... #define ENCTYPE_AES256_CTS_STRIBOG_256 0x001b /**< NO RFC */ #define ENCTYPE_AES256_CTS_STRIBOG_512 0x001c /**< NO RFC */ #define ENCTYPE_GOST89_CBC_STRIBOG_256 0x001d /**< SOME GOST */ #define ENCTYPE_GOST89_CBC_STRIBOG_512 0x001e /**< SOME GOST */ #define ENCTYPE_UNKNOWN 0x01ff ... #define CKSUMTYPE_STRIBOG_256_AES256 -139 /**< NO RFC */ #define CKSUMTYPE_STRIBOG_512_AES256 -140 /**< NO RFC */ #define CKSUMTYPE_STRIBOG_256_GOST89 -141 /**< SOME GOST */ #define CKSUMTYPE_STRIBOG_512_GOST89 -142 /**< SOME GOST */
Опишем структуру связок функции хеширования и шифрования, как в прошлом разделе:
// src/lib/crypto/krb/cksumtypes.c const struct krb5_cksumtypes krb5int_cksumtypes_list[] = { ... { CKSUMTYPE_STRIBOG_256_GOST89, "stribog-256-gost89", { 0 }, "STRIBOG256 GOST89 key", &krb5int_enc_gost89, &krb5int_hash_stribog256, krb5int_dk_checksum, NULL, 64, 32, 0 }, { CKSUMTYPE_STRIBOG_512_GOST89, "stribog-512-gost89", { 0 }, "STRIBOG512 GOST89 key", &krb5int_enc_gost89, &krb5int_hash_stribog512, krb5int_dk_checksum, NULL, 64, 64, 0 }, }; // src/lib/crypto/krb/etypes.c // тут я использовал только функции по умолчанию, но вы можете попробовать поэкспериментировать с различными другими функциями, например, с такими же как в aes const struct krb5_keytypes krb5int_enctypes_list[] = { ... { ENCTYPE_GOST89_CBC_STRIBOG_256, "gost89-cbc-stribog-256", { "gost89-stribog256" }, "GOST 28147-89 CBC mode with 256-bit stribog", &krb5int_enc_gost89, &krb5int_hash_stribog256, 16, krb5int_dk_crypto_length, krb5int_dk_encrypt, krb5int_dk_decrypt, krb5int_dk_string_to_key, k5_rand2key_direct, krb5int_dk_prf, CKSUMTYPE_STRIBOG_256_GOST89, 0 /*flags*/, 256 }, { ENCTYPE_GOST89_CBC_STRIBOG_512, "gost89-cbc-stribog-512", { "gost89-stribog512" }, "GOST 28147-89 CBC mode with 512-bit stribog", &krb5int_enc_gost89, &krb5int_hash_stribog512, 16, krb5int_dk_crypto_length, krb5int_dk_encrypt, krb5int_dk_decrypt, krb5int_dk_string_to_key, k5_rand2key_direct, krb5int_dk_prf, CKSUMTYPE_STRIBOG_512_GOST89, 0 /*flags*/, 256 }, };
Добавим в список режимов шифрования по умолчанию в файле src/lib/krb5/krb/init_ctx.c:
static krb5_enctype default_enctype_list[] = { ... ENCTYPE_AES256_CTS_STRIBOG_256, ENCTYPE_AES256_CTS_STRIBOG_512, ENCTYPE_GOST89_CBC_STRIBOG_256, ENCTYPE_GOST89_CBC_STRIBOG_512, 0 };
После этих манипуляций можно попробовать протестировать нововведенный алгоритм. Тестирование происходит аналогичным образом, что и в прошлом разделе. Имя связки можно взять в виде алиаса (например, gost89-stribog512) или с помощью имени самого алгоритма (например, gost89-cbc-stribog-512). Надеюсь, что все заработает, иначе не забывайте о том, что я сказал ранее.
Добавление алгоритма цифровой подписи
Ура! Мы приступаем к финальному разделу данной статьи и попробуем добавить собственный алгоритм электронной подписи. Не стоит пугаться, его добавить проще чем что-либо еще, так что давайте поскорее приступим… Хотя нет, подождите, для начала небольшая ремарка.
Асимметричное шифрование, достаточно, тяжеловесная штука. Одна из его главных особенностей – возможность аутентификации: все это построено на удостоверяющих центрах, сертификатах и всякой-всякой сложной лабуде. Как говорится...
Ребята, не стоит вскрывать эту тему. Вы молодые, шутливые, вам все легко. Это не то. Это не Чикатило и даже не архивы спецслужб. Сюда лучше не лезть. Серьезно, любой из вас будет жалеть. Лучше закройте тему и забудьте, что тут писалось. Я вполне понимаю, что данным сообщением вызову дополнительный интерес, но хочу сразу предостеречь пытливых — стоп. Остальные просто не найдут.
Как-то так. Ну что же, если у вас хватило смелости приступить к этому, то давайте начнем. В данной статье я покажу вам два способа получения и хранения приватных ключей и сертификатов: в файловой системе и на токене. В сущности, для обоих применима Аксиома Эскобара, но для второго способа потребуется дополнительный энджин, так что если вы решитесь пойти путем шиноби, то милости просим в данный раздел, остальные могут его пропустить.
Настройка openssl для работы с токенами (на примере Рутокен)
Для работы с Рутокенами, аппаратно поддерживающими ГОСТ-криптографию, для openssl существует rtengine. Его установка достаточно проста и не сильно отличается от GOST, нужно только знать, что и где брать.
Скачиваете отсюда SDK rutoken разработчика и достаете из папки sdk/openssl/rtengine/bin/ собранную под вашу платформу библиотеку с энжином и помещаете в папку с engine.
Скачать и установить модуль librtpkcs11ecp.so. отсюда
Собирать утилиту из ветки master OpenSC взяв коммит 8cf1e6f
После того как мы все это сделали, осталось настроить конфигурационный файл openssl.cnf:
# в начала файла написать openssl_conf = openssl_def ... # в конец файла # OpenSSL default section [openssl_def] engines = engine_section # Engine section [engine_section] gost = gost_section rtengine = rtengine_section # Engine gost section [gost_section] engine_id = gost dynamic_path = /usr/lib/x86_64-linux-gnu/engines-1.1/gost.so default_algorithms = ALL # Engine rtengine section [rtengine_section] engine_id = rtengine dynamic_path = /path/to/engine/librtengine.so MODULE_PATH = /path/to/module/librtpkcs11ecp.so RAND_TOKEN = pkcs11:manufacturer=Aktiv%20Co.;model=Rutoken%20ECP default_algorithms = CIPHERS, DIGEST, PKEY, RAND
Работоспособность engine мы проверим на деле.
Создание приватного ключа и сертификата для принципала, KDC и УЦ
За основу данной инструкции была взята инструкция по настройке kerberos. Я лишь адаптировал ее для работы с русскими алгоритмами, так что частично заглядывайте туда.
Создадим приватный ключ и сертификат удостоверяющего центра, а также приватный ключ, запрос на сертификат и сам сертификат, подписанный УЦ для KDC:
openssl genpkey -engine gost -algorithm gost2012_256 -pkeyopt paramset:B -out CA_key.pem # приватный ключ УЦ openssl req -engine gost -key CA_key.pem -new -x509 -out CA_cert.pem # самоподписанный сертификат УЦ openssl genpkey -engine gost -algorithm gost2012_256 -pkeyopt paramset:B -out KDC_key.pem # приватный ключ KDC openssl req -engine gost -new -out KDC.req -key ./KDC_key.pem # заявка на подписание сертификата для KDC # !!! ТУТ ВАЖНО СОЗДАТЬ РАСШИРЕНИЕ pkinit_extensions ИЗ ИНСТРУКЦИИ ВЫШЕ REALM=AKTIV-TEST.RU; export REALM # устанавливаем домен KDC CLIENT=127.0.0.1; export CLIENT # устанавливаем имя клиента, для которого подписываем сертификат (в нашем случае имя KDC). Я все воспроизвожу локально, поэтому ставлю localhost openssl x509 -engine gost -req -in ./KDC.req -CAkey ./CA_key.pem -CA ./CA_cert.pem -out ./KDC.pem -extfile ./pkinit_extensions -extensions kdc_cert -CAcreateserial # подписываем сертификат для KDC. sudo cp ./KDC.pem ./KDC_key.pem ./CA_cert.pem /var/lib/krb5kdc # отправляем все ключи и сертификаты у директорию kdc.
Изменим конфигурационный файл kdc, чтобы он знал, откуда брать ключи и сертификаты:
[kdcdefaults] ... pkinit_identity = FILE:/var/lib/krb5kdc/KDC.pem,/var/lib/krb5kdc/KDC_key.pem pkinit_anchors = FILE:/var/lib/krb5kdc/CA_cert.pem
[libdefaults]
spake_preauth_groups = edwards25519
3. Зададим принудительную предварительную аутентификацию для принципала:
```bash
sudo kadmin.local
kadmin.local$: modprinc +requires_preauth user
Создадим приватный ключ и заявку на сертификат для нашего принципала. Тут будет развилка, и надо выполнить разные действия в зависимости от того, где мы будем хранить приватный ключ: на токене или в ФС:
Для создания ключа и заявки в ФС все делаем почти аналогично, как для KDC:
openssl genpkey -engine gost -algorithm gost2012_256 -pkeyopt paramset:B -out client_key.pem # приватный ключ клиента openssl req -engine gost -new -out client.req -key ./client_key.pem # заявка на подписание сертификата для клиента
Для создания ключа на токене и подписания сертификата для него нужно выполнить следующее:
pkcs11-tool --module /path/to/module/librtpkcs11ecp.so --keypairgen --key-type GOSTR3410-2012-256:B -l --id 45 # создаем приватный и публичный ключ и помещаем их на токен и присваеваем id=45 openssl req -engine rtengine -new -key="pkcs11:id=E" -keyform engine -out client.req # создаем заявку на сертификат для ключа хранящегося на токене. E -- ascii запись 45
Теперь подпишем нашу заявку:
REALM=AKTIV-TEST.RU; export REALM # имя домена CLIENT=user; export CLIENT # имя пользователя, для которого подписываем сертификат openssl x509 -engine gost -CAkey ./CA_key.pem -CA ./CA_cert.pem -req -in ./client.req -extensions client_cert -extfile ./pkinit_extensions -out client.pem openssl x509 -engine gost -in client.pem -out client.crt -outform DER # Конвертируем сертификат из формата PEM в формат CRT
Теперь дело за малым: в первом случае загружаем сертификат в специальную директорию, во втором – на токен:
sudo cp ./client_key.pem client.pem /etc/krb5 # первый случай pkcs11-tool --module /usr/lib/librtpkcs11ecp.so -l -y cert -w ./client.crt --id 45 # второй случай (сертификат должен иметь тот же id, что и ключ)
Настраиваем файл конфигурации клиента (у меня это /etc/krb5.conf):
[libdefaults] ... pkinit_anchors = FILE:/var/lib/krb5kdc/CA_cert.pem # для аутентификации через ФС pkinit_identities = FILE:/etc/krb5/client.pem,/etc/krb5/client_key.pem # для аутентификации через токен #pkinit_identities = PKCS11:/usr/lib/librtpkcs11ecp.so
Надеюсь, что на данном этапе проблем не возникнет. Мы совсем близко! Добавим реализацию новых алгоритмов.
Добавление нового алгоритма цифровой подписи
Алгоритмы ЭЦП добавить куда проще чем те, что рассматривались ранее – придется заменить всего-то 2 файли! src/plugins/preauth/pkinit/pkcs11.h и src/plugins/preauth/pkinit/pkinit_crypto_openssl.c
Начнем с добавления идентификаторов новых механизмов и ключей в заголовочник pkcs11.h. Идентификатор механизма – это название алгоритма, который подается токену для того, чтобы он его совершил. Все эти идентификаторы стандартизированы и их можно найти в интернете (хоть и с большим трудом). Наши я нашел здесь в sdk/pkcs11/include/rtpkcs11t.h. Добавим в заголовочник следующие идентификаторы ключей и механизмов:
... #define CKK_TWOFISH (0x21) #define CKK_GOSTR3410 (0x30) #define CKK_GOSTR3411 (0x31) #define CKK_GOST28147 (0x32) #define CKK_VENDOR_DEFINED (1UL << 31) // A mask for new GOST algorithms. // For details visit https://tc26.ru/standarts/perevody/guidelines-the-pkcs-11-extensions-for-implementing-the-gost-r-34-10-2012-and-gost-r-34-11-2012-russian-standards-.html #define NSSCK_VENDOR_PKCS11_RU_TEAM (CKK_VENDOR_DEFINED | 0x54321000) #define CK_VENDOR_PKCS11_RU_TEAM_TK26 NSSCK_VENDOR_PKCS11_RU_TEAM #define CKK_GOSTR3410_512 (CK_VENDOR_PKCS11_RU_TEAM_TK26 | 0x003) ... #define CKM_AES_MAC_GENERAL (0x1084) #define CKM_AES_CBC_PAD (0x1085) #define CKM_GOSTR3410_KEY_PAIR_GEN (0x1200UL) #define CKM_GOSTR3410 (0x1201UL) #define CKM_GOSTR3410_WITH_GOSTR3411 (0x1202UL) #define CKM_GOSTR3410_KEY_WRAP (0x1203UL) #define CKM_GOSTR3410_DERIVE (0x1204UL) #define CKM_GOSTR3410_512_KEY_PAIR_GEN (CK_VENDOR_PKCS11_RU_TEAM_TK26 | 0x005) #define CKM_GOSTR3410_512 (CK_VENDOR_PKCS11_RU_TEAM_TK26 | 0x006) #define CKM_GOSTR3410_12_DERIVE (CK_VENDOR_PKCS11_RU_TEAM_TK26 | 0x007) #define CKM_GOSTR3410_WITH_GOSTR3411_12_256 (CK_VENDOR_PKCS11_RU_TEAM_TK26 | 0x008) #define CKM_GOSTR3410_WITH_GOSTR3411_12_512 (CK_VENDOR_PKCS11_RU_TEAM_TK26 | 0x009) #define CKM_GOSTR3411 (0x1210UL) #define CKM_GOSTR3411_HMAC (0x1211UL) #define CKM_GOSTR3411_12_256 (CK_VENDOR_PKCS11_RU_TEAM_TK26 | 0x012) #define CKM_GOSTR3411_12_512 (CK_VENDOR_PKCS11_RU_TEAM_TK26 | 0x013) #define CKM_GOSTR3411_12_256_HMAC (CK_VENDOR_PKCS11_RU_TEAM_TK26 | 0x014) #define CKM_GOSTR3411_12_512_HMAC (CK_VENDOR_PKCS11_RU_TEAM_TK26 | 0x015) #define CKM_GOST28147_KEY_GEN (0x1220UL) #define CKM_GOST28147_ECB (0x1221UL) #define CKM_GOST28147 (0x1222UL) #define CKM_GOST28147_MAC (0x1223UL) #define CKM_GOST28147_KEY_WRAP (0x1224UL)
Всеми ими мы не воспользуемся, но на будущее можно добавить.
В файле pkinit_crypto_openssl.c, в первую очередь, нужно добавить подгрузку энджинов перед началом работы. Вызов этой функции также нужно вставить перед get_key, т.к. эта функция почему-то вызывается перед подгрузкой энджинов:
#include <dirent.h> #include <arpa/inet.h> #include <openssl/engine.h> static ENGINE *eng = NULL; krb5int_init_engines() { if (eng) return; OPENSSL_add_all_algorithms_conf(); ERR_load_crypto_strings(); if (!(eng = ENGINE_by_id("rtengine"))) { printf("Engine rtengine doesn’t exist"); return; } ENGINE_init(eng); ENGINE_set_default(eng, ENGINE_METHOD_ALL); if (!(eng = ENGINE_by_id("gost"))) { printf("Engine gost doesn’t exist"); return; } ENGINE_init(eng); ENGINE_set_default(eng, ENGINE_METHOD_ALL); } ... get_key(krb5_context context, pkinit_identity_crypto_context id_cryptoctx, char *filename, const char *fsname, EVP_PKEY **retkey, const char *password) { ... krb5_error_code retval; krb5int_init_engines(); ... } ... int pkinit_openssl_init() { /* Initialize OpenSSL. */ ERR_load_crypto_strings(); OpenSSL_add_all_algorithms(); krb5int_init_engines(); return 0; }
После инициализации энджинов можно приступить к замене механизмов и электронной подписи. В данный момент жестко зашит только один алгоритм – RSA с хешом, полученным из sha1. Мы же встроим выбор между возможными режимами в зависимости от типа ключа, хранящегося на токене или в ФС. Для этого введем несколько функций, которые будут на вход получать контексты шифрования, а на выходе выдавать необходимые идентификаторы алгоритмов и механизмов. Также будет необходимо немного подправить функцию получения хендла приватного ключа с токена, т.к. она возвращает только RSA ключи:
// закомментируем фильтр на тип приватного ключа krb5_error_code pkinit_find_private_key(pkinit_identity_crypto_context id_cryptoctx, CK_ATTRIBUTE_TYPE usage, CK_OBJECT_HANDLE *objp) { ... true_false = TRUE; attrs[nattrs].type = usage; attrs[nattrs].pValue = &true_false; attrs[nattrs].ulValueLen = sizeof true_false; nattrs++; #endif // keytype = CKK_RSA; // attrs[nattrs].type = CKA_KEY_TYPE; // attrs[nattrs].pValue = &keytype; // attrs[nattrs].ulValueLen = sizeof keytype; // nattrs++; ... } // функция получения типа идентификатора алгоритма цифровой подписи в зависимости от полученного типа ключа: static int ckk_key_to_nid(CK_KEY_TYPE type) { switch(type){ case CKK_GOSTR3410: return NID_id_GostR3410_2012_256; case CKK_GOSTR3410_512: return NID_id_GostR3410_2012_512; default: return NID_rsa; } } // функция, возвращающая идентификатор алгоритма цифровой подписи, если он хранится на токене: static int pkinit_get_pkey_type(krb5_context context, pkinit_identity_crypto_context id_cryptoctx) { CK_OBJECT_HANDLE obj; CK_ATTRIBUTE attrs[1]; CK_KEY_TYPE key_type; int r; // открываем сессию: if (pkinit_open_session(context, id_cryptoctx)) { pkiDebug("can't open pkcs11 session\n"); return NID_rsa; } // находим приватный ключ: if (pkinit_find_private_key(id_cryptoctx, CKA_SIGN, &obj)) { return NID_rsa; } // вытаскиваем тип ключа: attrs[0].type = CKA_KEY_TYPE; attrs[0].pValue = &key_type; attrs[0].ulValueLen = sizeof (key_type); if ((r = id_cryptoctx->p11->C_GetAttributeValue(id_cryptoctx->session, obj, attrs, 1)) != CKR_OK) { pkiDebug("C_GetAttributeValue: %s\n Used RSA\n", pkinit_pkcs11_code_to_text(r)); return NID_rsa; } // возвращаем идентификатор алгоритма: return ckk_key_to_nid(key_type); } // функция, возвращающая идентификатор алгоритма хеширования в зависимости от того, какой алгоритм цифровой подписи используется: static int pkey_to_digest_nid(const EVP_PKEY* const pkey) { switch (EVP_PKEY_id(pkey)) { case NID_id_GostR3410_2012_256: return NID_id_GostR3411_2012_256; case NID_id_GostR3410_2012_512: return NID_id_GostR3411_2012_512; case NID_id_GostR3410_2001: return NID_id_GostR3411_2012_256; default: return NID_sha1; } } // функция, возвращающая идентификатор алгоритма хеширования для данного контекста: static int get_digest_nid(krb5_context context, const pkinit_identity_crypto_context id_cryptctx) { int nid; // если ключ указан (он находится в ФС), значит достаем NID оттуда, иначе из токена if (id_cryptctx->my_key) { nid = EVP_PKEY_id(id_cryptctx->my_key); } else { nid = pkinit_get_pkey_type(context, id_cryptctx); } switch (nid) { case NID_id_GostR3410_2012_256: return NID_id_GostR3411_2012_256; case NID_id_GostR3410_2012_512: return NID_id_GostR3411_2012_512; case NID_id_GostR3410_2001: return NID_id_GostR3411_2012_256; default: return NID_sha1; } } // функция, возвращающая идентификатор алгоритма цифровой подписи: static int get_alg_nid(krb5_context context, const pkinit_identity_crypto_context id_cryptctx) { int nid; if (id_cryptctx->my_key) { nid = EVP_PKEY_id(id_cryptctx->my_key); } else { nid = pkinit_get_pkey_type(context, id_cryptctx); } switch (nid) { case NID_id_GostR3410_2012_256: return NID_id_tc26_signwithdigest_gost3410_2012_256; case NID_id_GostR3410_2012_512: return NID_id_tc26_signwithdigest_gost3410_2012_512; case NID_id_GostR3410_2001: return NID_id_tc26_signwithdigest_gost3410_2012_256; default: return NID_sha1WithRSAEncryption; } } // функция возвращающая идентификатор механизма: static CK_MECHANISM_TYPE get_mech_type(krb5_context context, const pkinit_identity_crypto_context id_cryptctx) { int nid; if (id_cryptctx->my_key) { nid = EVP_PKEY_id(id_cryptctx->my_key); } else { nid = pkinit_get_pkey_type(context, id_cryptctx); } switch (nid) { case NID_id_GostR3410_2012_256: return CKM_GOSTR3410_WITH_GOSTR3411_12_256; case NID_id_GostR3410_2012_512: return CKM_GOSTR3410_WITH_GOSTR3411_12_512; case NID_id_GostR3410_2001: return CKM_GOSTR3410_WITH_GOSTR3411_12_256; default: return CKM_RSA_PKCS; } }
Теперь лишь осталось в функциях создания цифровой подписи cms_signeddata_create и create_signature заменить жесткую установку алгоритма на выбор алгоритма в зависимости от контекста:
krb5_error_code cms_signeddata_create(krb5_context context, pkinit_plg_crypto_context plg_cryptoctx, pkinit_req_crypto_context req_cryptoctx, pkinit_identity_crypto_context id_cryptoctx, int cms_msg_type, int include_certchain, unsigned char *data, unsigned int data_len, unsigned char **signed_data, unsigned int *signed_data_len) { ... /* Set digest algs */ p7si->digest_alg->algorithm = OBJ_nid2obj( get_digest_nid(context, id_cryptoctx)); ... p7si->digest_enc_alg->algorithm = OBJ_nid2obj(get_alg_nid(context, id_cryptoctx)); ... EVP_DigestInit_ex(ctx, EVP_get_digestbynid(get_digest_nid(context, id_cryptoctx)), NULL); ... alen = (unsigned int )ASN1_item_i2d((ASN1_VALUE *) sk, &abuf, ASN1_ITEM_rptr(PKCS7_ATTR_SIGN)); ... // заменяем проверку механизма, на проверку идентификатора алгоритма хеширования (сделать это в двух местах): if (id_cryptoctx->pkcs11_method == 1 && get_digest_nid(context, id_cryptoctx) == NID_sha1) { } static krb5_error_code create_signature(unsigned char **sig, unsigned int *sig_len, unsigned char *data, unsigned int data_len, EVP_PKEY *pkey) { ... EVP_SignInit(ctx, EVP_get_digestbynid(pkey_to_digest_nid(pkey))); ... } // узнаем механизм теперь на этапе создания подписи: static krb5_error_code pkinit_sign_data_pkcs11(krb5_context context, pkinit_identity_crypto_context id_cryptoctx, unsigned char *data, unsigned int data_len, unsigned char **sig, unsigned int *sig_len) { ... mech.mechanism = get_mech_type(context, id_cryptoctx); mech.pParameter = NULL; mech.ulParameterLen = 0; ... }
После всех этих манипуляций вы можете попробовать зайти, используя токен или сертификат в файловой системе (во втором случае, вероятно, потребуются права рута):
sudo kinit user
Если после запроса пароля токена не потребовалось вводить пароль user, значит, все отработало правильно.
Все замечания и вопросы вы можете писать в комментариях, а я постараюсь на них оперативно ответить.
Комментарии (8)
pilniy
21.09.2019 04:51В академических целях — интересно.
А практическая значимость?lo1ol Автор
21.09.2019 09:32Насколько мне известно, в госучреждениях часто стремиться использовать только отечественные криптографические алгоритмы. Эта статья поможет сисадминам перевести и Kerberos на ГОСТы.
vov_i
21.09.2019 09:40Возможно Вы удивитесь, но «основано на реальных событиях». Правда дело происходило под виндой. Некий регулятор потребовал у некого заказчика, чтобы доменная аутентификация была реализована на ГОСТ-алгоритмах. Под виндой вопрос решается при помощи ГОСТового CSP, а вот под Линуксами требуются такие танцы.
Мне самому такие требования от регулятора показались странными, но проще было сделать, чем объяснить, почему не.pilniy
21.09.2019 12:32В двойне странно. Тот же «регулятор» (как вы все таки красиво называете паразита :) должен был потребовать «заключение о корректности встраивания».
gotch
22.09.2019 22:08И поэтому бесплатной реализацией обойтись не получается, надо купить коммерческую реализацию, с сертификатом соответствия от Аль Эфесби.
Barsik68
Спасибо за статью!
Одна просьба заменить устаревший термин ЭЦП, который официально не используется с 2014 года.
Термин «электронная цифровая подпись» (сокращенно – ЭЦП), ставший привычным для большинства граждан, считается устаревшим. Он был введен в 2002 г. с принятием Федерального закона №1-ФЗ «Об электронной цифровой подписи» и утратил силу с 01.01.2014 г. после вступления в силу Федерального закона от 6 апреля 2011 г.63-ФЗ «Об электронной подписи».
lo1ol Автор
Учту замечание, но в статье используется неформальный стиль изложения, так что искажения смысла от этого не должно возникать